[前編]LaravelとVue.jsを使ってリアルタイムChatアプリを作ってみた。

Laravel

対象読者

・phpの基礎は理解している
・Laravelを使って簡単なアプリを実装したい
・Vue.jsを使った開発について知りたい
・Pusherの使い方を理解したい

始めに

udemyの講座を参考にしています。(無料ですが、講師がインドの方で全て英語です)
講座のlaravelのバージョンが5.4と少し古いので、8.xでも実装できるようにしています。
ログインの機能については解説されていなかったので、この記事でログイン部分の解説を入れています。

Chatプロジェクトの作成

laravelプロジェクトの作成

表示するページの作成

bootstrapインストール

Vue.jsインストール
を行います。

laravelプロジェクトの作成

以下のコマンドでlaravelプロジェクトを作成
laravel new Chat
Chatフォルダへ移動
cd Chat

php artisan serve
ノードパッケージモジュールをインストール
npm install


npm run dev

ファイルの作成

resources/views/chat.blade.php
※以下のhtmlコードをphpファイルで作成

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Document</title>
 <link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
 <div class="container">
  <div class="row">
   <h1>Chat room</h1>
  </div>
 </div>
 <script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

 

routes/web.php

<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
 return view('welcome');
});

Route::get('chat', function(){
 return view('chat');
});

bootstrapインストール

bootstrapインストール
npm install bootstrap

npm install

npm run dev
※もし以下のようなエラーが出る場合
ERROR in ./node_modules/bootstrap/dist/js/bootstrap.esm.js 6:0-41
Module not found: Error: Can’t resolve ‘@popperjs/core’ in ‘D:\GitHub\chat\node_modules\bootstrap\dist\js’

以下を実行

npm i @popperjs/core

Vue.jsのインストール

composerコマンドでフロントエンド開発のベースを簡単にしてくれます。

composer require laravel/ui


以下のコマンドでvue.jsを使えるようにします。

php artisan ui vue –auth

※ –authオプションを付けると認証機能も同時にインストールされます。
※講座では上記2つコマンドを実行していないですが、こちらを実行しておくと楽です。

再度、npm run devを実行

Chat(メッセージ)送信機能の実装

resources/views/chat.blade.php
入力したメッセージを表示する<message>タグとメッセージを入力する<input>タグを追加。

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Document</title>
 <meta name="csrf-token" content="{{csrf_token()}}">
 <link rel="stylesheet" href="{{ asset('css/app.css') }}">
 <style>
  .list-group{
   overflow-y: scroll;
   height: 200px;
  }
 </style>
</head>
<body>
 <div class="container">
  <div class="row" id="app">
   <h1>Chat room</h1>
   <div class="offset-4 col-md-4">
    <li class="list-group-item active">Chat</li>
    <ul class="list-group">
     <message v-for="value in chat.message">
      @{{value}}
     </message>
    </ul>
    <input type="text" class="form-control" placeholder="Type your message here.." v-model='message' @keyup.enter='send'>
   </div>
  </div>
 </div>
 <script src="{{ asset('js/app.js') }}"></script>
</body>
</html>


resources/js/app.js
app.jsファイルをjsフォルダ内に作成する。

require('./bootstrap');

window.Vue = require('vue').default;

Vue.component('message', require('./components/Message.vue').default);

const app = new Vue({
 el: '#app',
 data:{
  message:'',
  chat:{
   message:[]
  }
 },
 methods:{
  send(){
   if(this.message.length !=0){
    this.chat.message.push(this.message);
    this.message = '';
   }
  },
 }
});


resources/components/Message.vue

<template>
 <li class="list-group-item "><slot></slot></li>
</template>

<script>
 export default {
  mounted() {
   console.log('Component mounted.')
  }
 }
</script>

php artisan serve
http://127.0.0.1:8000/chat

Chat1

テキスト入力欄とスクロールバーが表示されていればOKです。
テキストを何回か入力すると以下のような感じになります。

Chat2

 

スクール追従機能の実装

このサイトに書いてある通りに以下を実行していきます。
https://www.npmjs.com/package/vue-chat-scroll

 npm install –save vue-chat-scroll


resources/js/app.js
以下のコードを追加する。

import Vue from 'vue'
import VueChatScroll from 'vue-chat-scroll'
Vue.use(VueChatScroll)

chat.blade.php
v-chat-scrollを<ul>タグの中に記載する。

<ul class="list-group" v-chat-scroll>

Event to Broadcast

laravel Echo(JavaScriptライブラリ)が、ブラウザ内でeventを受信できるように、laravel event をbroadcastするサーバ側ドライバーによって実行されます。
laravelですぐに使用できるbroadcastするドライバーにPusherチャンネルとRedisがあり、今回Pusherを利用します。(※Pusherについては後述)

参考ドキュメント
※これ以降もlaravel公式ドキュメントに従い、実装していきます。

以下のコマンドを実行

composer require pusher/pusher-php-server


config/app.php内で以下のコメントアウトを外す。

App\Providers\BroadcastServiceProvider::class,

App/Providers/EventServiceProvider.php

<?php
namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
// Registered::class => [
// SendEmailVerificationNotification::class,
// ],
  'App\Events\ChatEvent' => [
   'App\Listener\ChatListener',
  ],
];

 /**
* Register any events for your application.
*
* @return void
*/
public function boot(){
//
}
}

 

以下のコマンドを実行

php artisan event:generate

EventsとListenerフォルダがapp内に作成される。

app-foler

Eventsフォルダに作成されたChatEvent.phpを開いて、ShoudBroadcastを実装。

class ChatEvent

class ChatEvent implements ShouldBroadcast

 

app/Events/ChatEvent.php
※以下を追加。
use App\Models\User;

use Illuminate\Queue\SerializesModels;
use App\Models\User;
class ChatEvent implements ShouldBroadcast
{
  use Dispatchable, InteractsWithSockets, SerializesModels;
  public $message;
  public $user;

  public function __construct($message, User $user)
  {
   return $this->message = $message;
   return $this->user = $user;
  }

  public function broadcastOn()
  {
   return new PrivateChannel('chat');
  }
}

 

コントローラの作成

php artisan make:controller ChatController

 app/Http/Controllers/ChatController.php
chatとsend関数を作る。

<?php
namespace App\Http\Controllers;
use App\Events\ChatEvent;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class ChatController extends Controller
{
 public function chat(){
  return view('chat');
 }

 public function send(Request $request){
  $user=User::find(Auth::id());
  event(new ChatEvent($request->message,$user));
 }
}

 

ログイン機能

http://127.0.0.1:8000/login

login


ログインページはあるものの、テーブルがないため、DBを作る。

phpmyadminを使用想定。(使い方はここでは省略)

chatというDBを作る。

.envを確認。デフォルトのポート番号が3306なら以下のように設定。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=chat
DB_USERNAME=root
DB_PASSWORD=

以下のコマンドを実行するとdatabase\migrationsの中にあるファイルの分テーブルが作成される。

php artisan migrate:fresh –seed

phpmyadminのchatにusersテーブルが作成されていることを確認。

tables

http://127.0.0.1:8000/register

ユーザーを登録する。
user-register
以下のように表示されればOK
logged-in

routes/web.php
以下のように記載することで、/chatにアクセスしたときに認証するようになります。

Route::group(['prefix'=> '/', 'middleware'=>'auth'],function(){
 Route::get('chat', [App\Http\Controllers\ChatController::class,'chat']);
});

※講座ではChatController内で認証チェックをするようにしています。

Pusherを使ったリアルタイム通知機能の実装

Pusherという無料サービスを利用して、eventをbroadcastするドライバー使えるようにしていきます。
googleアカウントで無料登録する。
Channels の「Get Started」をクリック

dashboard-pusher

以下のようにapp名を入力、frontにvue.js、backendにlaravelを選択。

「Create app」後、以下のような画面が表示される。

dashboard-pusher-chat-app

左側にある「App Keys」をクリック。

app-keys

app_id,key,secretの値を.envファイルの以下にそれぞれ設定。

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=

clusterの値は
config/broadcasting.php
内のclusterに設定する。

'cluster' => 'ap3',


laravel echoのインストール

npm install –save-dev laravel-echo pusher-js

 

resources/js/bootstrap.js
コメントアウトを外す。
keyとclusterの値を.envと同じようにpusherのApp keysで確認した値に変更する。

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: true
});

※講座ではforceTLSがencryptedとなっていますが8.xの公式ドキュメントではforceTLSとなっているので、そのままでOKかと思います。

ChatControllerのsend関数を一旦以下のような関数にする。

    public function send(){
        $message = 'Hello';
        $user=User::find(Auth::id());
        event(new ChatEvent($message,$user));
    }    


web.phpのルーティングに以下を追加。

Route::get('send', [App\Http\Controllers\ChatController::class,'send']);


npm run dev またはnpm run watchを実行

php artisan serve

http://127.0.0.1:8000/send
(画面はないので、白画面が表示)

PusherにてDebug consoleをクリック

Debug-console

ChatController内に記載した固定messageの”Hello”が表示されていることを確認する。

ChatEvent.phpのコンスタント内のreturnを消す。

    public function __construct($message, User $user)
    {
        $this->message = $message;
        $this->user = $user;
    }

再度
http://127.0.0.1:8000/send

pusher側のconsoleにてuser情報が全て表示される。

userのnameだけをpusherのconsoleに表示したい場合、
app/Events/ChatEvent.phpのコンスタント内を以下にする。

    public function __construct($message, User $user)
    {
        $this->message = $message;
        $this->user =$user->name;
    }

 

/sendでpusherに送られたメッセージをコンソールに表示

pusher側で新しいchannelを作っておく。
channel:chat
Event:ChatEvent

Debug-chat-channel

 

routes/channels.php
に以下を追加する。

Broadcast::channel('chat', function(){
    return true;
});


resources/js/app.js

に以下をmounted()を追加

    mounted(){
        window.Echo.private('chat')
        .listen('ChatEvent', (e) => {
            console.log(e);
        });
    }

※講座ではwindowがついてないですが、ないとおそらくエラーになります。

http://127.0.0.1:8000/send
(画面はないので、白画面が表示)

http://127.0.0.1:8000/chat
側に固定メッセージがコンソール上に表示される

send-echo


axiosを使ってメッセージを送信する

resources/js/bootstrap.jsにaxiosが記載されていれば、インストールコマンドを実行する必要はないです。

bootstrap.js-axios

※ない場合
npm install axios

resources/js/app.js
axios.postを追加。

 methods:{
  send(){
   if(this.message.length !=0){
   //console.log(this.message);
    this.chat.message.push(this.message);
    axios.post('/send', {
     message: this.message
    })
    .then(response => {
      console.log(response);
      this.message = '';
    })
    .catch(error => {
      console.log(error);
    });
   }
  }
 },
 mounted(){
  window.Echo.private('chat')
   .listen('ChatEvent', (e) => {
     this.chat.message.push(e.message);
     console.log(e);
   });
 }


app/Http/Controllers/ChatController.php
send関数を以下のようにする。

 public function send(Request $request){
  $user=User::find(Auth::id());
  event(new ChatEvent($request->message,$user));
 }


この状態で異なるブラウザを2つ並べて、片方にメッセージを入力すると、入力した方(右側)は、2つメッセージが表示され、もう片方(左側)は、1つメッセージが表示されます。

右側は、自分が入力したメッセージとbroadcastされたものが表示されてます。
左側は、broadcastされたもののみが表示されてます。

2messages

自分が入力したものが重複して表示されないようにします。

app/Events/ChatEvent.php

 public function __construct($message, User $user)
{
 $this->message = $message;
 $this->user = $user->name;
 $this->dontBroadcastToCurrentUser();
}

$this->dontBroadcastToCurrentUser();
この関数を使うことで、入力したときにメッセージが2回表示されなくなります。

 

resources/js/app.js
自分が入力したメッセージと相手方でユーザー名が識別されるようにしていきます。

const app = new Vue({
 el: '#app',
 data:{
  message:'',
  chat:{
   message:[],
   user:[],
   color:[]
  }
 },
 methods:{
  send(){
   if(this.message.length !=0){
   //console.log(this.message);
    this.chat.message.push(this.message);
    this.chat.color.push('success');
    this.chat.user.push('you');
    axios.post('/send', {
     message: this.message
    })
    .then(response => {
      console.log(response);
      this.message = '';
    })
    .catch(error => {
      console.log(error);
    });
   }
  }
 },
 mounted(){
  window.Echo.private('chat')
   .listen('ChatEvent', (e) => {
    this.chat.message.push(e.message);
    this.chat.user.push(e.user);
    this.chat.color.push('warning');
    console.log(e);
   });
 }
});

dataにuserとcolorを追加、send()内について

this.chat.color.push('success');  //success(緑色)で表示
this.chat.user.push('you'); //youは自分が送る場合、常にyouを表示

mounted()内にも同様に追加

 this.chat.user.push(e.user); //メッセージを受信した方にユーザーの名前を表示
 this.chat.color.push('warning');  //黄色で表示

 

resources/components/Message.vue
propsに’user’を追加

<template>
<div>
<li class="list-group-item" :class="className"><slot></slot></li>
<small class="float-right" :class="className">{{user}}</small>
</div>
</template>

<script>
 export default {
  props:{
   'color':{},
   'user':{}
  },

youを{{user}}に変更

<small class="float-right" :class="className">{{user}}</small>

 

resources/views/chat.blade.php

<body>
 <div class="container">
  <div class="row" id="app">
   <h1>Chat room</h1>
   <div class="offset-4 col-md-4">
    <li class="list-group-item active">Chat</li>
    <ul class="list-group" v-chat-scroll>
     <message v-for="value,index in chat.message" :key=value.index :color=chat.color[index] :user=chat.user[index]>
      @{{value}}
     </message>
    </ul>
    <input type="text" class="form-control" placeholder="Type your message here.." v-model='message' @keyup.enter='send'>
   </div>
  </div>
 </div>
 <script src="{{ asset('js/app.js') }}"></script>
</body>

<message>タグ内にcolorとuserを追加

ブラウザを2つようにしてそれぞれ違うユーザーでログインした状態で、それぞれメッセージを送信すると、自分のメッセージは緑(success)、名前はyou、受信した側のメッセージは黄色(warning)、名前はユーザー名(送信したログインユーザー)になったことを確認。

chatting

※名前が左に表示されていますが、bootstrapがうまくいってなかったので、上の方に記載したコマンドを後で実行しなおしています。(本来なら右側に表示されます)

 

次回記事
[後編]LaravelとVue.jsを使ってリアルタイムChatアプリを作ってみた。