12/08/2018, 16:40

Chat room với laravel 5.5 và Vue.js trong 15 phút

Ở bài trước mình đã giới thiệu đến các bạn cách kết hợp Laravel với Vue.js trong bài viết: Simple CRUD Project. Bài này ta sẽ thử làm một ứng dụng chat room sử dụng Laravel Broadcasting, Pusher kết hợp với Vue.js. Mục tiêu của bài viết là như sau: I. Setup Tương tự như bài hướng dẫn ...

  • Ở bài trước mình đã giới thiệu đến các bạn cách kết hợp Laravel với Vue.js trong bài viết: Simple CRUD Project. Bài này ta sẽ thử làm một ứng dụng chat room sử dụng Laravel Broadcasting, Pusher kết hợp với Vue.js. Mục tiêu của bài viết là như sau:

I. Setup

  • Tương tự như bài hướng dẫn trước ta cũng cần tạo project laravel, tạo liên kết database và test thử vue component.
  • Bước 1: Tạo project laravel chatdemo lệnh sau: laravel new chatdemo
  • Bước 2: Chạy lệnh npm install và lệnh npm run watch
  • Bước 3: Tạo view chat như sau: resources/views/chat.blade.php
    @extends('layouts.app')
    
    @section('content')
    <div class="container">
        <div class="row">
            <div class="panel panel-default">
                <div class="panel-heading">
                    Chatroom
                </div>
    
                <div class="panel-body">
                    <example></example>
                </div>
            </div>
        </div>
    </div>
    @endsection
    
    • Và thêm vào routes/web.php:
      Route::get('/chat', function () {
          return view('chat');
      });
      
  • Bước 4: Chạy lệnh php artisan serve và truy cập vào đường link https://localhost:8000/chat check Example component

II. Vue Components

  • Phần này ta sẽ tạo ra các vue components: ChatLog.vue, ChatMessage.vueChatComposer.vue tương tự như resources/assets/js/components/Example.vue mặc định của laravel (tham khảo VueJS component docs)
  • Trước tiên ta cần thêm các components vào resources/js/app.js
    Vue.component('chat-log', require('./components/ChatLog.vue'));
    Vue.component('chat-message', require('./components/ChatMessage.vue'));
    Vue.component('chat-composer', require('./components/ChatComposer.vue'));
    
    const app = new Vue({
        el: '#app',
        data: {
            messages: [
                {
                    message: 'Hey!',
                    user: "Van Giang"
                },
                {
                    message: 'Hello!',
                    user: "Hong Quan"
                }
            ]
        },
        methods: {
            addMessage(message) {
                // Add to existing messages
                this.messages.push(message);
                // Persist to the database etc
            }
        }
    });
    
  • 2 message được tạo sẵn bởi Giang và Quân (phần sau các message này sẽ được load ra từ DB) dùng cho ChatLog component, còn method addMessage sẽ được gọi trong ChatComposer component
  • resources/assets/js/components/ChatLog.vue : Hiển thị danh sách các messages
    <template lang="html">
      <div class="chat-log">
          <chat-message v-for="message in messages" :message="message"></chat-message>
      </div>
    </template>
    
    <script>
    export default {
        props: ['messages']
    }
    </script>
    
    <style lang="css">
    .chat-log .chat-message:nth-child(even) {
        background-color: #ccc;
    }
    </style>
    
  • resources/assets/js/components/ChatMessage.vue : hiển thị 1 message, được gọi từ ChatLog.vue thông qua thẻ chat-message
    <template lang="html">
      <div class="chat-message">
        <p>{{ message.message }}</p>
        <small>{{ message.user }}</small>
      </div>
    </template>
    
    <script>
    export default {
        props: ['message']
    }
    </script>
    
    <style lang="css">
    .chat-message {
        padding: 1rem;
    }
    .chat-message > p {
        margin-bottom: .5rem;
    }
    </style>
    
  • resources/assets/js/components/ChatComposer.vue : tạo 1 message mới
    <template lang="html">
      <div class="chat-composer">
          <input type="text" placeholder="Start typing your message..." v-model="messageText" @keyup.enter="sendMessage">
          <button class="btn btn-primary" @click="sendMessage">Send</button>
      </div>
    </template>
    
    <script>
    export default {
        data() {
            return {
                messageText: '
            }
        },
        methods: {
            sendMessage() {
                this.$emit('messagesent', {
                    message: this.messageText,
                    user: "Van Giang"
                });
                this.messageText = ';
            }
        }
    }
    </script>
    
    <style lang="css">
    .chat-composer {
        display: flex;
    }
    .chat-composer input {
        flex: 1 auto;
    }
    .chat-composer button {
        border-radius: 0;
    }
    </style>
    
  • Giờ ta chỉ việc xóa bỏ example component trong chat.blade.php và gọi đến ChatLog, ChatComposer component
    <chat-log :messages="messages"></chat-log>
    <chat-composer v-on:messagesent="addMessage"></chat-composer>
    

III. Laravel Backend

  • Chạy lệnh php artisan make:auth
  • Tạo model Message cùng file migrate với lệnh: php artisan make:model Message -m
  • Bảng messages sẽ có trường message và user_id
    Schema::create('messages', function (Blueprint $table) {
        $table->increments('id');
        $table->timestamps();
        $table->text('message');
        $table->integer('user_id')->unsigned();
    });
    
  • Tạo quan hệ 1-N giữa bảng users và bảng messages:
    class Message extends Model
    {
        protected $fillable = ['message'];
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    }
    
    //app/User.php
    public function messages()
    {
        return $this->hasMany(Message::class);
    }
    
  • Tạo database và chạy command tạo bảng: php artisan migrate
  • Thêm các routes vào routes/web.php
    Route::get('/chat', function () {
        return view('chat');
    })->middleware('auth');
    
    Route::get('/messages', function () {
        return AppMessage::with('user')->get();
    })->middleware('auth');
    
    Route::post('/messages', function () {
        // Store the new message
        $user = Auth::user();
        $user->messages()->create([
            'message' => request()->get('message')
        ]);
        return ['status' => 'OK'];
    })->middleware('auth');
    
    Auth::routes();
    Route::get('/home', 'HomeController@index');
    
  • Thay vì hardcode message như phần II thì tới đây ta sẽ load message từ DB và insert message mới vào DB, update file app.js:
    const app = new Vue({
        el: '#app',
        data: {
            messages: []
        },
        methods: {
            addMessage(message) {
                // Add to existing messages
                this.messages.push(message);
    
                // Persist to the database etc
                axios.post('/messages', message).then(response => {
                    // Do whatever;
                })
            }
        },
        created() {
            axios.get('/messages').then(response => {
                this.messages = response.data;
            });
        }
    });
    
  • Biến messages giờ đây sẽ có dạng như sau:
    messages = [
        message: "Hello",
        user: {
            name: "Van Giang"
        }
    ];
    
  • Nên ta cần update lại ChatComposer.vue: tên của người gửi được lấy từ dropdown góc cao bên phải:
    methods: {
        sendMessage() {
            this.$emit('messagesent', {
                message: this.messageText,
                user: {
                    name: $('.navbar-right .dropdown-toggle').text()
                }
            });
            this.messageText = ';
        }
    }
    ...
    ...
    .chat-composer input {
      flex: 1 auto;
     padding: .5rem 1rem;
    }
    
  • Thêm thiện thị cho trường hợp chưa có message nào trong DB: ChatLog.vue
    <chat-message v-for="message in messages" :message="message"></chat-message>
    <div class="empty" v-show="messages.length === 0">
       Nothing here yet!
    </div>
    ...
    .empty {
      padding: 1rem;
      text-align: center;
    }
    
  • update ChatMessage.vue
    <p>{{ message.message }}</p>
    <small>{{ message.user.name }}</small>
    

IV. Laravel Echo

  • Bước này sẽ làm cho ứng dụng trở nên realtime, bạn cần nắm được được một vài kiến thức cơ bản về Event Broadcasting và tạo ra 1 app tại Pusher

  • Tạo app Pusher, truy cập vào https://pusher.com và tạo 1 app tương tự như sau: ta cần lưu ý những thông tin sau để dùng trong ứng dụng chatroom:

    app_id = "446989"
    key = "xxxxxxxxxxxxxx"
    secret = "xxxxxxxxxxxxxx"
    cluster = "ap1"
    
  • Tạo event MessagePosted.php với lệnh: php artisan make:event MessagePosted

    namespace AppEvents;
    
    use AppUser;
    use AppMessage;
    use IlluminateBroadcastingChannel;
    use IlluminateQueueSerializesModels;
    use IlluminateBroadcastingPrivateChannel;
    use IlluminateBroadcastingPresenceChannel;
    use IlluminateFoundationEventsDispatchable;
    use IlluminateBroadcastingInteractsWithSockets;
    use IlluminateContractsBroadcastingShouldBroadcast;
    
    class MessagePosted implements ShouldBroadcast
    {
        use Dispatchable, InteractsWithSockets, SerializesModels;
    
        public $message;
        public $user;
    
        /**
         * Create a new event instance.
         *
         * @return void
         */
        public function __construct(Message $message, User $user)
        {
            $this->message = $message;
            $this->user = $user;
        }
    
        /**
         * Get the channels the event should broadcast on.
         *
         * @return IlluminateBroadcastingChannel|array
         */
        public function broadcastOn()
        {
            return new PresenceChannel('chatroom');
        }
    }
    
  • Đăng ký channel trong routes/channel.php:

    Broadcast::channel('chatroom', function ($user) {
        return $user;
    });
    
  • Event này được gọi khi 1 message mới được tạo trong routes/web.php

    use AppEventsMessagePosted;
    
    Route::post('/messages', function () {
        $user = Auth::user();
    
        $message = $user->messages()->create([
            'message' => request()->get('message')
        ]);
    
        broadcast(new MessagePosted($message, $user))->toOthers();
    
        return ['status' => 'OK'];
    })->middleware('auth');
    
  • Cài đặt Pusher PHP SDK: composer require pusher/pusher-php-server "~3.0"

  • Cài đặt Laravel Echo: npm install --save laravel-echo pusher-js

  • Uncomment và truyền key ở trên vào resources/assets/js/bootstrap.js

    import Echo from 'laravel-echo'
    
    window.Pusher = require('pusher-js');
    
    window.Echo = new Echo({
        broadcaster: 'pusher',
        key: 'your-pusher-key',
        cluster: 'ap1',
        encrypted: true
    });
    
  • Tất cả cấu hình broadcasting được đặt trong file config/broadcasting.php

    'pusher' => [
        'driver' => 'pusher',
        'key' => env('PUSHER_APP_KEY'),
        'secret' => env('PUSHER_APP_SECRET'),
        'app_id' => env('PUSHER_APP_ID'),
        'options' => [
          'cluster' => 'ap1',
          'encrypted' => true,
        ],
    ],
    
    • ta chỉ cần truyền app_key, app_secret, app_id, cluster vào file .env là xong (nhớ set BROADCAST_DRIVER=pusher).
  • Để thêm hiển thị số lượng người đang trong Room, ta cần chỉnh lại một chút file chat.blade.php

    @extends('layouts.app')
    
    @section('content')
    <div class="container">
        <div class="row">
            <div class="panel panel-default">
                <div class="panel-heading">
                    Chatroom
                    <span class="badge pull-right">@{{ usersInRoom.length }}</span>
                </div>
    
                <div class="panel-body">
                    <chat-log :messages="messages"></chat-log>
                    <chat-composer v-on:messagesent="addMessage"></chat-composer>
                </div>
            </div>
        </div>
    </div>
    @endsection
    
  • Và update file app.js để thực hiện việc update real-time

    ...
    data: {
        messages: [],
        usersInRoom: []
    },
    ...
    created() {
        axios.get('/messages').then(response => {
            this.messages = response.data;
        });
    
        Echo.join('chatroom')
            .here((users) => {
                this.usersInRoom = users;
            })
            .joining((user) => {
                this.usersInRoom.push(user);
            })
            .leaving((user) =>
                
                
                
             
0