Quản lý Real-time Notification trong Laravel với Pusher
Bài đăng này đã không được cập nhật trong 4 năm
Real-Time Notifications
Trong thực tế chúng ta gặp rất nhiều trường hợp cần phải sử dụng đến notifications, và đặc biệt là việc thông báo các notifications này là ngay lập tức (real-time).
Một cách thường gặp nhất đó là sử dụng AJAX request liên tục đến back-end và lấy thông báo mới nhất về nếu có. Tốt hơn chúng ta có thể sử dụng WebSockets để nhận các notifications, đây cũng là cách mà mình muốn đề cập trong bài viết này
Pusher
Pusher là một dịch vụ cloud, cung cấp các hàm xử lý dữ liệu với thời gian thực thông qua WebSockets gửi đến web và các ứng dụng mobile.
Nó đơn giản là một API, để sử dụng dễ dàng trong bài viết này chúng ta sẽ sử dụng thêm Laravel Broadcasting và Laravel Echo. Trong bài viết này chúng ta sẽ thêm real-time notification và 1 blog đã tồn tại (clone từ trên git về)
Khởi tạo project
1. Clone 1 blog Laravel
git clone https://github.com/marslan-ali/laravel-blog
2. Chỉnh sửa các biến config
cp .env.example .env
###Edit file .env
DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
3. Install dependencies và migrate database
composer install
php artisan migrate --seed
4. Tạo bảng followers quan hệ n-n với bảng users
php artisan make:migration create_followers_table --create=followers
Tạo migrate file quan hệ với bảng users
public function up()
{
Schema::create('followers', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->index();
$table->integer('follows_id')->index();
$table->timestamps();
});
}
Chạy migrate để tạo table
php artisan migrate
*Thêm quan hệ với bảng User, thêm code vào User model
// ...
class extends Authenticatable
{
// ...
public function followers()
{
return $this->belongsToMany(self::class, 'followers', 'follows_id', 'user_id')
->withTimestamps();
}
public function follows()
{
return $this->belongsToMany(self::class, 'followers', 'user_id', 'follows_id')
->withTimestamps();
}
// Helper function
public function follow($userId)
{
$this->follows()->attach($userId);
return $this;
}
// Helper function
public function unfollow($userId)
{
$this->follows()->detach($userId);
return $this;
}
// Helper function
public function isFollowing($userId)
{
return (boolean) $this->follows()->where('follows_id', $userId)->first(['id']);
}
}
Tạo các thành phần liên quan đến Users
1. Add routes
/...
Route::group(['middleware' => 'auth'], function () {
Route::get('users', 'UsersController@index')->name('users');
Route::post('users/{user}/follow', 'UsersController@follow')->name('follow');
Route::delete('users/{user}/unfollow', 'UsersController@unfollow')->name('unfollow');
});
2. Tạo UserController và các file blade liên quan
php artisan make:controller UsersController
Add index function vào UserController.php
// ...
use App\User;
class UsersController extends Controller
{
//..
public function index()
{
$users = User::where('id', '!=', auth()->user()->id)->get();
return view('users.index', compact('users'));
}
}
Add user.index.php blade
@extends('layouts.app')
@section('content')
<div class="container">
<div class="col-sm-offset-2 col-sm-8">
<!-- Following -->
<div class="panel panel-default">
<div class="panel-heading">
All Users
</div>
<div class="panel-body">
<table class="table table-striped task-table">
<thead>
<th>User</th>
<th> </th>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td clphpass="table-text"><div>{{ $user->name }}</div></td>
@if (auth()->user()->isFollowing($user->id))
<td>
<form action="{{route('unfollow', ['id' => $user->id])}}" method="POST">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button type="submit" id="delete-follow-{{ $user->id }}" class="btn btn-danger">
<i class="fa fa-btn fa-trash"></i>Unfollow
</button>
</form>
</td>
@else
<td>
<form action="{{route('follow', ['id' => $user->id])}}" method="POST">
{{ csrf_field() }}
<button type="submit" id="follow-user-{{ $user->id }}" class="btn btn-success">
<i class="fa fa-btn fa-user"></i>Follow
</button>
</form>
</td>
@endif
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
3. Follow và Unfollow
Thêm function follow và unfollow và UserController
//...
class UsersController extends Controller
{
//...
public function follow(User $user)
{
$follower = auth()->user();
if ($follower->id == $user->id) {
return back()->withError("You can't follow yourself");
}
if(!$follower->isFollowing($user->id)) {
$follower->follow($user->id);
// sending a notification
$user->notify(new UserFollowed($follower));
return back()->withSuccess("You are now friends with {$user->name}");
}
return back()->withError("You are already following {$user->name}");
}
public function unfollow(User $user)
{
$follower = auth()->user();
if($follower->isFollowing($user->id)) {
$follower->unfollow($user->id);
return back()->withSuccess("You are no longer friends with {$user->name}");
}
return back()->withError("You are not following {$user->name}");
}
}
Notifications
Laravel cung cấp API để gửi thông báo thông qua nhiều kênh khác nhau: Emails, SMS, web notifications, và bất kỳ loại notifications nào thông qua Notification class.
Chúng ta có 2 loại thông báo chính:
- Follow notification: gửi đến người dùng khi bạn được người khác theo dõi
- Thông báo tạo bài viết: được gửi đến người dùng khi họ tạo 1 bài viết
1. User Followed Notification
Tạo table notifications
php artisan notifications:table
php artisan migrate
Tạo table UserFollowed
php artisan make:notification UserFollowed
Thêm vào file app/Notifications/UserFollowed.php
Các user được followed sẽ được thêm 1 bản ghi trong DB thông qua function via
class UserFollowed extends Notification implements ShouldQueue
{
use Queueable;
protected $follower;
public function __construct(User $follower)
{
$this->follower = $follower;
}
public function via($notifiable)
{
return ['database'];
}
public function toDatabase($notifiable)
{
return [
'follower_id' => $this->follower->id,
'follower_name' => $this->follower->name,
];
}
}
Thêm follow function vào file UserController.php
// ...
use App\Notifications\UserFollowed;
class UsersController extends Controller
{
// ...
public function follow(User $user)
{
$follower = auth()->user();
if ( ! $follower->isFollowing($user->id)) {
$follower->follow($user->id);
// add this to send a notification
$user->notify(new UserFollowed($follower));
return back()->withSuccess("You are now friends with {$user->name}");
}
return back()->withSuccess("You are already following {$user->name}");
}
//...
}
2. Đánh dấu các thông báo đã được đọc
Thông báo sẽ chưa thông tin và link đến nguồn. Ví dụ: khi người dùng nhận được thông báo tạo bài viết mới, thông báo sẽ bao gồm thông tin thông báo (text) , link đến bài viết đó khi bạn click vào và 1 cờ đánh dấu là "read"
Tạo 1 middleware như sau:
php artisan make:middleware MarkNotificationAsRead
Thêm code handle như sau:
class MarkNotificationAsRead
{
public function handle($request, Closure $next)
{
if($request->has('read')) {
$notification = $request->user()->notifications()->where('id', $request->read)->first();
if($notification) {
$notification->markAsRead();
}
}
return $next($request);
}
}
Add middleware to Kernel (app/Http/Kernel.php)
//...
class Kernel extends HttpKernel
{
//...
protected $middlewareGroups = [
'web' => [
//...
\App\Http\Middleware\MarkNotificationAsRead::class,
],
// ...
];
//...
}
3. Hiển thị notifications
Chúng ta sẽ sử dụng AJAX để hiển thị notification và PUSHER để quản lý real-time. Trước tiên ta thêm function notifications vào UserController
// ...
class UsersController extends Controller
{
// ...
public function notifications()
{
// return 5 notifications unread
return auth()->user()->unreadNotifications()->limit(5)->get()->toArray();
}
}
Add route to function
//...
Route::group([ 'middleware' => 'auth' ], function () {
// ...
Route::get('/notifications', 'UsersController@notifications');
});
Add code vào file resources/views/layouts/app.blade.php
<head>
<!-- // ... // -->
<!-- Scripts -->
<script>
window.Laravel = <?php echo json_encode([
'csrfToken' => csrf_token(),
]); ?>
</script>
<!-- This makes the current user's id available in javascript -->
@if(!auth()->guest())
<script>
window.Laravel.userId = <?php echo auth()->user()->id; ?>
</script>
@endif
</head>
<body>
<!-- // ... // -->
@if (Auth::guest())
<li><a href="{{ url('/login') }}">Login</a></li>
<li><a href="{{ url('/register') }}">Register</a></li>
@else
<!-- // add this dropdown // -->
<li class="dropdown">
<a class="dropdown-toggle" id="notifications" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span class="glyphicon glyphicon-user"></span>
</a>
<ul class="dropdown-menu" aria-labelledby="notificationsMenu" id="notificationsMenu">
<li class="dropdown-header">No notifications</li>
</ul>
</li>
<!-- // ... // -->
4. JavaScript and SASS
Chúng ta sẽ sử dụng Laravel Mix để complie JavaScript và SASS. Đầu tiên ta sẽ cài đặt npm packages
npm install
Thêm code vào file app/resources/assets/js/app.js
window._ = require('lodash');
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
var notifications = [];
const NOTIFICATION_TYPES = {
follow: 'App\\Notifications\\UserFollowed'
};
“GET” thông báo thông qua AJAX
//...
$(document).ready(function() {
// check if there's a logged in user
if(Laravel.userId) {
$.get('/notifications', function (data) {
addNotifications(data, "#notifications");
});
}
});
function addNotifications(newNotifications, target) {
notifications = _.concat(notifications, newNotifications);
// show only last 5 notifications
notifications.slice(0, 5);
showNotifications(notifications, target);
}
Hiển thị thông báo
//...
function showNotifications(notifications, target) {
if(notifications.length) {
var htmlElements = notifications.map(function (notification) {
return makeNotification(notification);
});
$(target + 'Menu').html(htmlElements.join(''));
$(target).addClass('has-notifications')
} else {
$(target + 'Menu').html('<li class="dropdown-header">No notifications</li>');
$(target).removeClass('has-notifications');
}
}
Thêm 1 số helper functions để tạo thông báo
//...
// Make a single notification string
function makeNotification(notification) {
var to = routeNotification(notification);
var notificationText = makeNotificationText(notification);
return '<li><a href="' + to + '">' + notificationText + '</a></li>';
}
// get the notification route based on it's type
function routeNotification(notification) {
var to = '?read=' + notification.id;
if(notification.type === NOTIFICATION_TYPES.follow) {
to = 'users' + to;
}
return '/' + to;
}
// get the notification text based on it's type
function makeNotificationText(notification) {
var text = '';
if(notification.type === NOTIFICATION_TYPES.follow) {
const name = notification.data.follower_name;
text += '<strong>' + name + '</strong> followed you';
}
return text;
}
Thêm css cho notification app/resources/assets/sass/app.scss
//...
#notifications.has-notifications {
color: #bf5329
}
Complie asset
npm run dev
Thông báo bài viết mới
1. Tạo table NewPost và add code vào model
php artisan make:notification NewPost
Thêm code vào file app/Notifications/NewPost.php
// ..
use App\Post;
use App\User;
class NewArticle extends Notification implements ShouldQueue
{
// ..
protected $following;
protected $post;
public function __construct(User $following, Post $post)
{
$this->following = $following;
$this->post = $post;
}
public function via($notifiable)
{
return ['database'];
}
public function toDatabase($notifiable)
{
return [
'following_id' => $this->following->id,
'following_name' => $this->following->name,
'post_id' => $this->post->id,
];
}
}
2. Sử dụng Eloquent Observers để quản lý notifications
Thêm code vào app/Observers/PostObserver.php
namespace App\Observers;
use App\Notifications\NewPost;
use App\Post;
class PostObserver
{
public function created(Post $post)
{
$user = $post->user;
foreach ($user->followers as $follower) {
$follower->notify(new NewPost($user, $post));
}
}
}
Đăng ký observer với AppServiceProvider
//...
use App\Observers\PostObserver;
use App\Post;
class AppServiceProvider extends ServiceProvider
{
//...
public function boot()
{
Post::observe(PostObserver::class);
}
//...
}
Add code to js file app/resources/assets/js/app.js
// ...
const NOTIFICATION_TYPES = {
follow: 'App\\Notifications\\UserFollowed',
newPost: 'App\\Notifications\\NewPost'
};
//...
function routeNotification(notification) {
var to = `?read=${notification.id}`;
if(notification.type === NOTIFICATION_TYPES.follow) {
to = 'users' + to;
} else if(notification.type === NOTIFICATION_TYPES.newPost) {
const postId = notification.data.post_id;
to = `posts/${postId}` + to;
}
return '/' + to;
}
function makeNotificationText(notification) {
var text = '';
if(notification.type === NOTIFICATION_TYPES.follow) {
const name = notification.data.follower_name;
text += `<strong>${name}</strong> followed you`;
} else if(notification.type === NOTIFICATION_TYPES.newPost) {
const name = notification.data.following_name;
text += `<strong>${name}</strong> published a post`;
}
return text;
}
Quản lý notification real-time với Puser
1. Config Pusher
Truy cập trang web https://pusher.com/ , đăng ký 1 acct free và tạo một app
Edit file config .env
...
BROADCAST_DRIVER=pusher
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=
Thêm config ở file config/broadcasting.php
//...
'connections' => [
'pusher' => [
//...
'options' => [
'cluster' => 'eu',
'encrypted' => true
],
],
//...
Đăng ký với App\Providers\BroadcastServiceProvider vào trong file config/app.php
// ...
'providers' => [
// ...
App\Providers\BroadcastServiceProvider
//...
],
//...
2. Cài đặt Pusher SDK và Laravel Echo
Cài đặt Pusher
composer require pusher/pusher-php-server
npm install --save laravel-echo pusher-js
Add code vào file app/Notifications/UserFollowed.php
//...
class UserFollowed extends Notification implements ShouldQueue
{
// ..
public function via($notifiable)
{
return ['database', 'broadcast'];
}
//...
public function toArray($notifiable)
{
return [
'id' => $this->id,
'read_at' => null,
'data' => [
'follower_id' => $this->follower->id,
'follower_name' => $this->follower->name,
],
];
}
}
Thêm code vào file app/Notifications/NewPost.php
//...
class NewPost extends Notification implements ShouldQueue
{
//...
public function via($notifiable)
{
return ['database', 'broadcast'];
}
//...
public function toArray($notifiable)
{
return [
'id' => $this->id,
'read_at' => null,
'data' => [
'following_id' => $this->following->id,
'following_name' => $this->following->name,
'post_id' => $this->post->id,
],
];
}
}
Update js file (app/resources/assets/js/app.js)
// ...
window.Pusher = require('pusher-js');
import Echo from "laravel-echo";
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-key',
cluster: 'eu',
encrypted: true
});
var notifications = [];
//...
$(document).ready(function() {
if(Laravel.userId) {
//...
window.Echo.private(`App.User.${Laravel.userId}`)
.notification((notification) => {
addNotifications([notification], '#notifications');
});
}
});
Và cuối cùng chúng ta cũng hoàn thành, notification sẽ được gửi và quản lý real-time. Bạn có thể thử dùng và xem cách các thông báo được quản lý và update.
Tổng kết
Pusher là một service đơn giản có thể quản lý thông bảo real-time một cách dễ dàng. Pusher kết hợp với Laravel notification sẽ tiện lợi hơn, chúng ta có thể gửi notification thông qua nhiều kênh khác nhau (email, SMS, Slack,v..v..).
Bài viết trên mô tả cách hoạt động và kết hợp của Puser vào Laravel. Chúng ta còn có thể làm nhiều việc hơn nữa từ việc kết hợp Pusher và Laravel: như gửi thông báo song song, gửi đến các thiết bị mobile, IOT v..v...
Tham khảo
All rights reserved