鍍金池/ 教程/ PHP/ 事件
門面
Laravel Homestead
安裝及配置
測(cè)試
HTTP 中間件
加密
升級(jí)指南
幫助函數(shù)
應(yīng)用目錄結(jié)構(gòu)
集合
新手入門指南-簡(jiǎn)單任務(wù)管理系統(tǒng)
任務(wù)調(diào)度
查詢構(gòu)建器
視圖
驗(yàn)證
Laravel Cashier(訂購(gòu)&支付&發(fā)票)
本地化
隊(duì)列
調(diào)整器
分頁(yè)
文件系統(tǒng)/云存儲(chǔ)
貢獻(xiàn)代碼
哈希
HTTP 控制器
緩存
遷移
HTTP 請(qǐng)求
Laravel Elixir
發(fā)行版本說(shuō)明
Envoy 任務(wù)運(yùn)行器(SSH任務(wù))
序列化
Session
起步
帶用戶功能的任務(wù)管理系統(tǒng)
起步
用戶授權(quán)
郵件
事件
填充數(shù)據(jù)
HTTP 路由
服務(wù)提供者
Blade 模板引擎
包開(kāi)發(fā)
用戶認(rèn)證
Artisan 控制臺(tái)
HTTP 響應(yīng)
集合
服務(wù)容器
關(guān)聯(lián)關(guān)系
一次請(qǐng)求的生命周期
契約
Redis
錯(cuò)誤&日志

事件

1、簡(jiǎn)介

Laravel 事件提供了簡(jiǎn)單的觀察者模式實(shí)現(xiàn),允許你訂閱和監(jiān)聽(tīng)?wèi)?yīng)用中的事件。事件類通常存放在 app/Events目錄,監(jiān)聽(tīng)器存放在 app/Listeners。

2、注冊(cè)事件/監(jiān)聽(tīng)器

Laravel 自帶的 EventServiceProvider為事件注冊(cè)提供了方便之所。其中的 listen屬性包含了事件(鍵)和對(duì)應(yīng)監(jiān)聽(tīng)器(值)數(shù)組。如果應(yīng)用需要,你可以添加多個(gè)事件到該數(shù)組。例如,讓我們添加 PodcastWasPurchased事件:

/**
 * 事件監(jiān)聽(tīng)器映射
 *
 * @var array
 */
protected $listen = [
    'App\Events\PodcastWasPurchased' => [
        'App\Listeners\EmailPurchaseConfirmation',
    ],
];

2.1 生成事件/監(jiān)聽(tīng)器類

當(dāng)然,手動(dòng)為每個(gè)事件和監(jiān)聽(tīng)器創(chuàng)建文件是很笨重的,取而代之地,我們可見(jiàn)簡(jiǎn)單添加監(jiān)聽(tīng)器和事件到 EventServiceProvider然后使用 event:generate命令。該命令將會(huì)生成羅列在 EventServiceProvider中的所有事件和監(jiān)聽(tīng)器。當(dāng)然,已存在的事件和監(jiān)聽(tīng)器不會(huì)被創(chuàng)建:

php artisan event:generate

3、定義事件

事件類是一個(gè)處理與事件相關(guān)的簡(jiǎn)單數(shù)據(jù)容器,例如,假設(shè)我們生成的 PodcastWasPurchased事件接收一個(gè) Eloquent ORM 對(duì)象:

<?php

namespace App\Events;

use App\Podcast;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;

class PodcastWasPurchased extends Event{
    use SerializesModels;

    public $podcast;

    /**
     * 創(chuàng)建新的事件實(shí)例
     *
     * @param  Podcast  $podcast
     * @return void
     */
    public function __construct(Podcast $podcast)
    {
        $this->podcast = $podcast;
    }
}

正如你所看到的,該事件類不包含任何特定邏輯,只是一個(gè)存放被購(gòu)買的 Podcast對(duì)象的容器,如果事件對(duì)象被序列化的話,事件使用的 SerializesModels trait 將會(huì)使用 PHP 的 serialize函數(shù)序列化所有 Eloquent模型。

4、定義監(jiān)聽(tīng)器

接下來(lái),讓我們看看我們的示例事件的監(jiān)聽(tīng)器,事件監(jiān)聽(tīng)器在 handle方法中接收事件實(shí)例,event:generate 命令將會(huì)自動(dòng)在 handle方法中導(dǎo)入合適的事件類和類型提示事件。在 handle方法內(nèi),你可以執(zhí)行任何需要的邏輯以響應(yīng)事件。

<?php

namespace App\Listeners;

use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class EmailPurchaseConfirmation{
    /**
     * 創(chuàng)建事件監(jiān)聽(tīng)器
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * 處理事件
     *
     * @param  PodcastWasPurchased  $event
     * @return void
     */
    public function handle(PodcastWasPurchased $event)
    {
        // Access the podcast using $event->podcast...
    }
}

你的事件監(jiān)聽(tīng)器還可以在構(gòu)造器中類型提示任何需要的依賴,所有事件監(jiān)聽(tīng)器通過(guò)服務(wù)容器解析,所以依賴會(huì)自動(dòng)注入:

use Illuminate\Contracts\Mail\Mailer;

public function __construct(Mailer $mailer){
    $this->mailer = $mailer;
}

停止事件繼續(xù)往下傳播 有時(shí)候,你希望停止事件被傳播到其它監(jiān)聽(tīng)器,你可以通過(guò)從監(jiān)聽(tīng)器的 handle方法中返回 false來(lái)實(shí)現(xiàn)。

4.1 事件監(jiān)聽(tīng)器隊(duì)列

需要將事件監(jiān)聽(tīng)器放到隊(duì)列中?沒(méi)有比這更簡(jiǎn)單的了,只需要讓監(jiān)聽(tīng)器類實(shí)現(xiàn) ShouldQueue接口即可,通過(guò) Artisan命令 event:generate生成的監(jiān)聽(tīng)器類已經(jīng)將接口導(dǎo)入當(dāng)前命名空間,所有你可以立即拿來(lái)使用:

<?php

namespace App\Listeners;

use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class EmailPurchaseConfirmation implements ShouldQueue{
    //
}

就是這么簡(jiǎn)單,當(dāng)監(jiān)聽(tīng)器被事件調(diào)用,將會(huì)使用 Laravel的隊(duì)列系統(tǒng)通過(guò)隊(duì)列分發(fā)器自動(dòng)隊(duì)列化。如果通過(guò)隊(duì)列執(zhí)行監(jiān)聽(tīng)器的時(shí)候沒(méi)有拋出任何異常,隊(duì)列任務(wù)在執(zhí)行完成后被自動(dòng)刪除。

4.1.1 手動(dòng)訪問(wèn)隊(duì)列

如果你需要手動(dòng)訪問(wèn)底層隊(duì)列任務(wù)的 deleterelease方法,在生成的監(jiān)聽(tīng)器中默認(rèn)導(dǎo)入的 Illuminate\Queue\InteractsWithQueuetrait 提供了訪問(wèn)這兩個(gè)方法的權(quán)限:

<?php

namespace App\Listeners;

use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class EmailPurchaseConfirmation implements ShouldQueue{
    use InteractsWithQueue;

    public function handle(PodcastWasPurchased $event)
    {
        if (true) {
            $this->release(30);
        }
    }
}

5、觸發(fā)事件

要觸發(fā)一個(gè)事件,可以使用 Event門面,傳遞一個(gè)事件實(shí)例到 fire方法,fire方法會(huì)分發(fā)事件到所有監(jiān)聽(tīng)器:

<?php

namespace App\Http\Controllers;

use Event;
use App\Podcast;
use App\Events\PodcastWasPurchased;
use App\Http\Controllers\Controller;

class UserController extends Controller{
    /**
     * 顯示指定用戶屬性
     *
     * @param  int  $userId
     * @param  int  $podcastId
     * @return Response
     */
    public function purchasePodcast($userId, $podcastId)
    {
        $podcast = Podcast::findOrFail($podcastId);

        // Purchase podcast logic...

        Event::fire(new PodcastWasPurchased($podcast));
    }
}

此外,你還可以使用全局的幫助函數(shù) event來(lái)觸發(fā)事件:

event(new PodcastWasPurchased($podcast));

6、廣播事件

在很多現(xiàn)代 web 應(yīng)用中,web 套接字被用于實(shí)現(xiàn)實(shí)時(shí)更新的用戶接口。當(dāng)一些數(shù)據(jù)在服務(wù)器上被更新,通常一條消息通過(guò) websocket 連接被發(fā)送給客戶端處理。

為幫助你構(gòu)建這樣的應(yīng)用,Laravel 讓通過(guò) websocket連接廣播事件變得簡(jiǎn)單。廣播 Laravel事件允許你在服務(wù)端和客戶端 JavaScript框架之間共享同一事件名。

6.1 配置

所有的事件廣播配置選項(xiàng)都存放在 config/broadcasting.php配置文件中。Laravel 支持多種廣播驅(qū)動(dòng):1Pusher、Redis以及一個(gè)服務(wù)于本地開(kāi)發(fā)和調(diào)試的日志驅(qū)動(dòng)。每一個(gè)驅(qū)動(dòng)都有一個(gè)配置示例。

6.1.1 廣播預(yù)備知識(shí)

事件廣播需要以下兩個(gè)依賴:

  • Pusher: pusher/pusher-php-server ~2.0
  • Redis: predis/predis ~1.0
  • 6.1.2 隊(duì)列預(yù)備知識(shí)

    在開(kāi)始介紹廣播事件之前,還需要配置并運(yùn)行一個(gè)隊(duì)列監(jiān)聽(tīng)器。所有事件廣播都通過(guò)隊(duì)列任務(wù)來(lái)完成以便應(yīng)用的響應(yīng)時(shí)間不受影響。

6.2 將事件標(biāo)記為廣播

要告訴 Laravel 給定事件應(yīng)該被廣播,需要在事件類上實(shí)現(xiàn) Illuminate\Contracts\Broadcasting\ShouldBroadcast 接口。ShouldBroadcast 接口要求你實(shí)現(xiàn)一個(gè)方法:broadcastOn。該方法應(yīng)該返回事件廣播”頻道“名稱數(shù)組:

<?php

namespace App\Events;

use App\User;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class ServerCreated extends Event implements ShouldBroadcast{
    use SerializesModels;

    public $user;

    /**
     * 創(chuàng)建新的事件實(shí)例
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * 獲取事件廣播頻道
     *
     * @return array
     */
    public function broadcastOn()
    {
        return ['user.'.$this->user->id];
    }
}

然后,你只需要和正常一樣觸發(fā)該事件,事件被觸發(fā)后,一個(gè)隊(duì)列任務(wù)將通過(guò)指定廣播驅(qū)動(dòng)自動(dòng)廣播該事件。

6.3 廣播數(shù)據(jù)

如果某個(gè)事件被廣播,其所有的 public屬性都會(huì)按照事件負(fù)載自動(dòng)序列化和廣播,從而允許你從 JavaScript中訪問(wèn)所有 public數(shù)據(jù),因此,舉個(gè)例子,如果你的事件有一個(gè)單獨(dú)的包含 Eloquent模型的$user 屬性,廣播負(fù)載定義如下:

{
    "user": {
        "id": 1,
        "name": "Jonathan Banks"
        ...
    }
}

然而,如果你希望對(duì)廣播負(fù)載有更加細(xì)粒度的控制,可以添加 broadcastWith方法到事件,該方法應(yīng)該返回你想要通過(guò)事件廣播的數(shù)組數(shù)據(jù):

/**
 * 獲取廣播數(shù)據(jù)
 *
 * @return array
 */
public function broadcastWith(){
    return ['user' => $this->user->id];
}

6.4 消費(fèi)事件廣播

6.4.1 Pusher

你可以通過(guò) PusherJavaScript SDK 方便地使用Pusher驅(qū)動(dòng)消費(fèi)事件廣播。例如,讓我們從之前的例子中消費(fèi) App\Events\ServerCreated事件:

this.pusher = new Pusher('pusher-key');

this.pusherChannel = this.pusher.subscribe('user.' + USER_ID);

this.pusherChannel.bind('App\\Events\\ServerCreated', function(message) {
    console.log(message.user);
});

6.4.2 Redis

如果你在使用 Redis廣播,你將需要編寫自己的 Redis pub/sub消費(fèi)者來(lái)接收消息并使用自己選擇的 websocket技術(shù)將其進(jìn)行廣播。例如,你可以選擇使用使用 Node編寫的流行的Socket.io庫(kù)。 使用 Node庫(kù) socket.ioioredis,你可以快速編寫事件廣播發(fā)布所有廣播事件:

var app = require('http').createServer(handler);
var io = require('socket.io')(app);

var Redis = require('ioredis');
var redis = new Redis();

app.listen(6001, function() {
    console.log('Server is running!');});

function handler(req, res) {
    res.writeHead(200);
    res.end('');}

io.on('connection', function(socket) {
    //
});

redis.psubscribe('*', function(err, count) {
    //
});

redis.on('pmessage', function(subscribed, channel, message) {
    message = JSON.parse(message);
    io.emit(channel + ':' + message.event, message.data);
});

7、事件訂閱者

事件訂閱者是指那些在類本身中訂閱到多個(gè)事件的類,從而允許你在單個(gè)類中定義一些事件處理器。訂閱者應(yīng)該定義一個(gè) subscribe方法,該方法中傳入一個(gè)事件分發(fā)器實(shí)例:

<?php

namespace App\Listeners;

class UserEventListener{
    /**
     * 處理用戶登錄事件
     */
    public function onUserLogin($event) {}

    /**
     * 處理用戶退出事件
     */
    public function onUserLogout($event) {}

    /**
     * 為訂閱者注冊(cè)監(jiān)聽(tīng)器
     *
     * @param  Illuminate\Events\Dispatcher  $events
     * @return array
     */
    public function subscribe($events)
    {
        $events->listen(
            'App\Events\UserLoggedIn',
            'App\Listeners\UserEventListener@onUserLogin'
        );

        $events->listen(
            'App\Events\UserLoggedOut',
            'App\Listeners\UserEventListener@onUserLogout'
        );
    }

}

7.1 注冊(cè)一個(gè)事件訂閱者

訂閱者被定義后,可以通過(guò)事件分發(fā)器進(jìn)行注冊(cè),你可以使用 EventServiceProvider上的$subcribe 屬性來(lái)注冊(cè)訂閱者。例如,讓我們添加 UserEventListener

<?php

namespace App\Providers;

use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider{
    /**
     * 事件監(jiān)聽(tīng)器映射數(shù)組
     *
     * @var array
     */
    protected $listen = [
        //
    ];

    /**
     * 要注冊(cè)的訂閱者
     *
     * @var array
     */
    protected $subscribe = [
        'App\Listeners\UserEventListener',
    ];
}
上一篇:升級(jí)指南下一篇:本地化