Pusher & Laravel Echo

Pusherとは?
-> WebSocketを介して、リアルタイムの双方向機能を統合し、迅速にするためのシンプルなホストされているAPI

参考: Laravel Broadcasting
https://readouble.com/laravel/6.x/ja/broadcasting.html
-> イベントをPusherチャンネルによりブロードキャストする
-> laravel echoとは、JavaScriptライブラリで、チャンネルの購読とLaravelによるイベントブロードキャストのリッスンを苦労なしに実現

### 1. Pusherにregister
https://pusher.com/

### 2 .env
pusherのapp keysからCredentialsを.envに追記

PUSHER_APP_ID=${}
PUSHER_APP_KEY=${}
PUSHER_APP_SECRET=${}
PUSHER_APP_CLUSTER=${}

BROADCAST_DRIVERをpusherに変更

BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

### 3. config/app.php
BroadcastServiceProviderのコメントアウト削除

        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // コメントアウト削除
        App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

### 4. Pusher Channels HTTP PHP Library
Pusher用のパッケージインストール
https://github.com/pusher/pusher-http-php

$ php composer.phar require pusher/pusher-php-server

### 5. Laravel Eco パッケージインストール
$ npm install –save laravel-echo pusher-js

### 6. resources/js/bootstrap.js
Laravel Echoに関するコードのコメント解除
forceTLSをfalseに変更

import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: false
});

build
$ npm run dev

### 7. app.jsに差し替え
前)

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>

後)

<script src="/js/app.js"></script>

### 8. イベント作成

$ php artisan make:event MessageCreated

app/Events/MessageCreated.php

namespace App\Events;
use App\Message;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class MessageCreated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public $message;

    public function __construct(Message $message)
    {
        //
        $this->message = $message;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('chat');
    }
}

### 9. Controller
event発火

public function create(Request $request)
    {
        //
        $message = Message::create([
            'body' => $request->message
        ]);
        event(new MessageCreated($message));
    }

### 10. config/broadcasting.php
‘useTLS’をtrueからfalseに変更

'connections' => [

        'pusher' => [
            'driver' => 'pusher',
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'app_id' => env('PUSHER_APP_ID'),
            'options' => [
                'cluster' => env('PUSHER_APP_CLUSTER'),
                'useTLS' => false,
            ],
        ],

### 11. view

<div id="chat">
		<textarea v-model="message"></textarea>
		<br>
		<button type="button" @click="send()">送信</button>

		<div v-for="m in messages">
			<span v-text="m.created_at"></span>:&nbsp;
			<span v-text="m.body"></span>
		</div>
	</div>
	<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
	<script src="/js/app.js"></script>
    <script>
    	new Vue({
    		el: '#chat',
    		data: {
    			message: '',
    			messages: []
    		},
    		methods: {
    			getMessages(){
    				const url = '/ajax/chat';
    				axios.get(url)
    					.then((response) => {
    						this.messages = response.data;
    					});
    			},
    			send(){
                    const axiosPost = axios.create({
                        xsrfHeaderName: "X-XSRF-TOKEN",
                        withCredentials: true
                    })
    				const url = '/ajax/chat';
    				const params = { message: this.message };
    				axiosPost.post(url, params)
    					.then((response) => {
    						this.message = '';
    					});
    			},
    		},
    		mounted(){
    			this.getMessages();
    			Echo.channel('chat')
    				.listen('MessageCreated', (e) =>{
    					this.getMessages();
    				});
    		} 
    	});
    </script>

※送信前

※送信後

JSコンソールと、storage/logs/laravel.logでエラー内容を確認しながら、修正していきます。
axiosのPOSTは、xsrfHeaderNameにX-XSRF-TOKENを指定してcsrf対策を施すようにします。

Laravel 6.xでEventを作成してPusherからメッセージ

### event作成
$ php artisan make:event Test
app/Events/Test.php

class Test implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public $message;

    public function __construct($message)
    {
        //
        $this->message = $message;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('test_channel');
    }

    public function broadcastAs()
    {
        return 'test_event';
    }
}

### config/broadcasting.php

'default' => env('BROADCAST_DRIVER', 'pusher'),
'connections' => [

        'pusher' => [
            'driver' => 'pusher',
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'app_id' => env('PUSHER_APP_ID'),
            'options' => [
                'cluster' => env('PUSHER_APP_CLUSTER'),
                'useTLS' => true,
            ],
        ],

### web.php

Route::get('/sent', function(){
	event(new \App\Events\Test('テストメッセージ'));
});

Route::get('receive', function(){
return <<<HTML

<!DOCTYPE html>
<head>
	<title>Pusher Test</title>
	<script src="https://js.pusher.com/3.2/pusher.min.js"></script>
	<script>
		Pusher.logToConsole = true;

		var pusher = new Pusher('******************',{
			cluster : 'ap3',
			forceTLS: true
		});

		var pusherChannel = pusher.subscribe('test_channel');

		pusherChannel.bind('test_event', function(data){
			alert(data.message);
		});
	</script>
</head>
HTML;
});

broadcastdriverが初期値のnullのままだと動かないので注意が必要です。

Laravel Event

公式
https://readouble.com/laravel/6.x/ja/events.html
– アプリケーションで発生する様々なイベントを購読し、リッスンする為に使用
– イベントクラスは、app/Eventsディレクトリに格納
– リスナは、app/Listenersディレクトリに保存

## Event利用の流れ
– Event定義
– Eventに対応するListenerを定義
– Eventを仕込む

### 1. app/Providers/EventServiceProvider.php
Eventと対応するListenerを登録する

protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        'App\Events\AccessDetection' => [
            'App\Listeners\AccessDetectionListener',
        ]
    ];

### 2. イベント/リスナ生成
$ php artisan event:generate
※EventServiceProvider.phpで定義していないと、作成されない。

app/Events/AccessDetection.php

public function __construct()
    {
        //
        $this->param = $value;
    }

app/Listeners/AccessDetectionListener.php

public function handle(AccessDetection $event)
    {
        //
        dump('Access Detected param=' . $event->param);
    }

### route

use App\Events\AccessDetection;
Route::get('event', function(){
	event(new AccessDetection(str_random(10)));
	return 'hoge';
})

$ php composer.phar require laravel/helpers
$ php composer.phar require –dev beyondcode/laravel-dump-server

$ php artisan dump-server

Laravel Var Dump Server
=======================

[OK] Server listening on tcp://127.0.0.1:9912

// Quit the server with CONTROL-C.

GET http://192.168.33.10:8000/event
———————————–

———— ——————————————-
date Thu, 06 Feb 2020 12:10:49 +0000
controller “Closure”
source AccessDetectionListener.php on line 30
file app/Listeners/AccessDetectionListener.php
———— ——————————————-

“Access Detected param=oWhPChPRSh”

なるほど、EventServiceProviderでEventを登録し、Eventsで定義したeventsをListenerで受け取り、controller(route)でevent(new ${eventName})と書いて、event発火の流れでしょうか。大まかな流れは理解しました。