Laravel8 + Vue + Pusherにてチャット構築

Laravel8 + Vueでのチャット機能を簡単にご紹介します。

開発環境

PHP v8.0.13
Laravel v8.73.2
npm v8.1.0

本記事ではPusherというサービスを使って構築していきます。
Laravel環境構築や、Pusherへの会員登録については他記事などを参考にしてみてください。

パッケージなどのインストール

$ composer require laravel/ui

Laravel UIをインストール。

$ php artisan ui vue --auth

スカフォールドを各フレームワークから選択。(今回はVue.jsを使用)

$ npm install

パッケージのインストール。

$ npm run watch

コンパイルの自動化。

$ php artisan migrate

マイグレーション実行。

Pusher連携部分の実装

App\Providers\BroadcastServiceProvider::class,

ブロードキャストを有効にするため、 config/app.php の上記コメントアウトを外す。

$ composer require pusher/pusher-php-server

Pusher用のパッケージのインストール。

$ npm install --save laravel-echo pusher-js

Laravel Echo用のパッケージをインストール。

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

resources/js/bootstrap.js の上記コメントアウトを外し、Laravel Echoを有効にします。

モデルの作成

php artisan make:model Message -m
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMessagesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->char('id', 36)->primary()->comment('ID');
            $table->char('user_id', 36);
            $table->text('message');
            $table->dateTime('deleted_at')->nullable()->comment('削除日時');
            $table->dateTime('created_at')->nullable()->comment('作成日時');
            $table->dateTime('updated_at')->nullable()->comment('更新日時');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('messages');
    }
}

作成されたマイグレーションファイルを上記のように修正。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    use HasFactory;

    protected $primaryKey = 'id';
    protected $keyType = 'string';
    public $incrementing = false;

    protected $fillable = ['user_id', 'message'];

    protected static function boot()
    {
        parent::boot();
        static::creating(function ($model) {
            $model->{$model->getKeyName()} = (string) \Str::uuid();
        });
    }

}

作成された app/Models/Message.php を上記のように修正。

public function messages()
{
    return $this->hasMany('App\Model\Message');
}

UserモデルにhasManyを追加。

$ php artisan migrate

マイグレーション実行。

Routeを追加

use App\Http\Controllers\MessageController;
...
Route::group(['middleware' => ['auth']], function () {
    Route::get('chat', [MessageController::class, 'index']);
    Route::get('messages', [MessageController::class, 'get']);
    Route::post('messages', [MessageController::class, 'send']);
});

routes/web.php に上記を追加。

Controllerを追加

$ php artisan make:controller MessageController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Events\MessageSentEvent;
use App\Models\Message;

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

    public function get()
    {
        return Message::with('user')->get();
    }

    public function send(Request $request)
    {
        $user = Auth::user();
 
        $message = $user->messages()->create([
            'message' => $request->input('message')
        ]);
 
        event(new MessageSent($user, $message));
 
        return ['status' => 'Message Success!'];
    }
}

Eventの作成

$ php artisan make:event MessageSentEvent
<?php

namespace App\Events;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\Message;

class MessageSentEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $message;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(User $user, Message $message)
    {
        $this->user = $user;
        $this->message = $message;
    }

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

公開チャンネルにしたい場合、上記を Channelに変更することで対応可能。
また、 ‘test’ 部分を書き換えることで複数チャンネルにも対応可能。

Broadcast::channel('chat', function ($user) {
    return Auth::check();
});

今回はプライベートチャンネルを実装したため、 routes/channels.php にて認証チェックを行う。

Viewの作成

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
 
<head>
    <meta charset="UTF-8">
    <title>テストチャット</title>
    <link href="{{ mix('css/app.css') }}" rel="stylesheet" type="text/css">
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>
 
<body>
    <div id="app">
        <chat-component></chat-component>
    </div>
 
    <script src="{{ mix('js/app.js') }}"></script>
</body>
 
</html>

resources/views/chat.blade.php

<template>
    <div>
        <ul>
            <li v-for="(message, key) in messages" :key="key">
                <strong>{{ message.user.name }}</strong>
                {{ message.message }}
            </li>
        </ul>
        <input v-model="text" />
        <button @click="postMessage" :disabled="!textExists">Submit</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            text: '',
            messages: []
        };
    },
    computed: {
        textExists() {
            return this.text.length > 0;
        }
    },
    created() {
        this.getMessages();
        Echo.private('test').listen('MessageSentEvent', e => {
            this.messages.push({
                message: e.message.message,
                user: e.user
            });
        });
    },
    methods: {
        getMessages() {
            axios.get('/messages').then(response => {
                this.messages = response.data;
            });
        },
        postMessage(message) {
            axios.post('/messages', { message: this.text }).then(response => {
                this.text = '';
            });
        }
    }
};
</script>

resources/js/components/ChatComponent.vue

Pusherの設定

Name your app : アプリ名
Select a cluster : 日本で使用する場合、そのままでOK
Front end : Vue.js
Back end : PHP

Pusherにログイン後、「Create my app」画面から各種情報を入力して登録。

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

登録が完了したら「App Keys」メニューから必要情報をコピーし、 .env ファイルに追記。

ログイン後、 /chat にアクセスすることでチャットが行えます。

Laravelの知ってると便利な機能

laravel
エイブリッジでは、Web開発の際、開発言語にPHP、フレームワークによくLaravelを使用しています。
いろいろあるPHPフレームワークの中で世界でもっとも人気であると言っても良いと思います。

そこで、知っているとちょっと便利なテクニックを紹介したいと思います。
(Laravel8をベース)

■データ取得編

日付で抽出

User::whereDate('created_at', '2021-11-11')->get();

年で抽出

User::whereYear('created_at', '2021')->get();

月で抽出

User::whereMonth('created_at', '11')->get();

日で抽出

User::whereDay('created_at', '11')->get();

NULLは最後になるようにソートする

Post::orderByRaw('published_at IS NULL ASC')
->orderBy('published_at')->get();

日付で整列

// 未指定の場合はcreated_atの降順となる。
Post::latest()->get();

// カラム名を指定するとそのカラムの降順となる。
Post::latest('published_at')->get();

Modelのタイムスタンプのみを更新

User::find(1)->touch();

JSON配列を検索する

/*
+-----------------------------------------------------------------------------------------------+
| id | tags |
+-----------------------------------------------------------------------------------------------+
| 1 | ["Android","iOS","Web"] |
+-----------------------------------------------------------------------------------------------+
*/
$tag = 'Web';
$posts = Post::whereRaw("JSON_CONTAINS(tags,'["{$tag}"]')")->get();

/*
+-----------------------------------------------------------------------------------------------+
| id | tags |
+-----------------------------------------------------------------------------------------------+
| 1 | [{"id":1,"tag":"Android"},{"id":2,"tag":"iOS"},{"id":2,"tag":"Web"}] |
+-----------------------------------------------------------------------------------------------+
*/
$tag = 'Web';
$posts = Post::whereRaw("JSON_CONTAINS(tags,'{"tag":{$tag}}')")->get();

■Eloquent編

Modelの変更された項目を確認する

$user = App\Models\User::first();
$user->name = ’taro yamada’;
$user->age = 30;

$result = $user->getChanges();
logger($result);

/* 結果:
array(
"name" => "taro yamada", "age" => 30
);
*/

Modelの元の値を確認する

$user = App\Models\User::first();
logger($user->name);

// 結果:hanako yamada

$user->name = ’taro yamada’;

$result = $user->getOriginal('name');
logger('original: '.$user->name);

// 結果:hanako yamada

属性のキャスト
モデルでキャストを設定しておくと、指定した型で返却されます。
例えば、日付項目に文字列で日付を設定しても、getするときは日付型で返却されます。

class Post extends Model
{
protected $casts = [
'category_no' => 'integer'
'published_at' => 'datetime'
];
}

インクリメント/デクリメント

$user = User::find($id);

// インクリメント(+1)
$user->increment('login_count');
// デクリメント(-1)
$user->decrement('login_count');
// インクリメント(+5)
$user->increment('login_count', 5);
// デクリメント(-5)
$user->decrement('login_count', 5);

短期のお仕事!【沖縄】

今回は短期のお仕事の募集です!

期間:11月8日~2021年1月14日まで。

業務内容:入力と書類チェック業務。

時間:9:00~18:00

勤務日:平日

時給:1050円

勤務地:沖縄県うるま市州崎

交通費:支給あり

最寄り駅:無料駐車場完備

ホームページよりエントリー可能です。