如何使用Pusher向Laravel添加实时通知

特别为“ Framework Laravel”课程的学生准备了这篇文章的翻译




现代的Web用户希望了解应用程序中发生的所有事情。您可能不想成为一个甚至没有通知下拉列表的网站,现在不仅可以在所有社交网站上找到这些通知,而且如今在几乎所有地方都可以找到。

幸运的是,使用Laravel和Pusher,此功能实现非常简单。

实时通知


为了提供积极的用户体验,应实时显示通知。一种方法是定期向服务器发送AJAX请求并接收最新的通知(如果有)。

最好的方法是使用WebSocket的功能并在发送通知时接收通知。这正是我们将在本文中实现的。

推杆


Pusher是一种Web服务,用于通过WebSocket将实时双向功能集成到Web和移动应用程序中。

它有一个非常简单的API,但是我们将通过Laravel Broadcasting和Laravel Echo使它使用起来更加容易。

在本文中,我们将向现有博客添加实时通知。

项目


初始化


首先,我们克隆一个简单的Laravel博客:

git clone   https://github.com/marslan-ali/laravel-blog

然后,我们将创建MySQL数据库并设置环境变量,以使应用程序可以访问数据库。

让我们拷贝env.example.env并更新与数据库相关的变量。

cp .env.example .envDB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

现在,使用以下命令安装项目依赖项

composer install

并运行migration and populate命令以使用一些数据填充数据库:

php artisan migrate --seed

如果运行该应用程序并转到/posts,则可以看到生成的帖子列表。

检查应用程序,注册用户并创建一些消息。这是一个非常简单的应用程序,但是非常适合演示。

订阅用户


我们希望给用户提供彼此订阅的机会,因此我们必须在用户之间建立多对多关系以实现这一点。

让我们创建一个将用户链接到用户的数据透视表。让我们进行新的迁移followers

php artisan make:migration create_followers_table --create=followers

我们需要在此迁移中添加几个字段:user_id代表订阅的用户,以及follows_id代表他们订阅的用户的字段

更新迁移,如下所示:

public function up()
{
    Schema::create('followers', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->index();
        $table->integer('follows_id')->index();
        $table->timestamps();
    });
}

现在让我们继续创建表:

php artisan migrate

让我们向模型添加关系方法User

// ...

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();
    }
}

现在,该模型User具有必要的关系,它followers返回用户的所有订户,并follows返回用户已预订的所有订户。

我们将需要一些辅助功能,这些功能允许用户订阅其他用户- follow,并检查该用户是否订阅了特定用户- isFollowing

// ...

class extends Authenticatable
{
    // ...

    public function follow($userId) 
    {
        $this->follows()->attach($userId);
        return $this;
    }

    public function unfollow($userId)
    {
        $this->follows()->detach($userId);
        return $this;
    }

    public function isFollowing($userId) 
    {
        return (boolean) $this->follows()->where('follows_id', $userId)->first(['id']);
    }

}

精细。准备好模型后,您需要列出用户列表。

用户列表


让我们从确定必要的路线开始。

/...
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');
});

然后是时候为用户创建一个新的控制器了:

php artisan make:controller UsersController

我们将添加一个方法index

// ...
use App\User;
class UsersController extends Controller
{
    //..
    public function index()
    {
        $users = User::where('id', '!=', auth()->user()->id)->get();
        return view('users.index', compact('users'));
    }
}

该方法需要介绍。让我们创建一个视图users.index,并在其中添加以下标记:

@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

现在,您可以访问该页面/users以查看用户列表。

关注和取消关注


UsersController缺少的方法follow方法上unfollow让我们实现它们以完成这一部分。

//...
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);

            //  
            $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}");
    }
}

我们已经完成了此功能。现在,我们可以在页面上订阅用户并取消订阅/users

通知事项


Laravel提供了一个用于通过多个渠道发送通知的API。可以使用Notification类发送电子邮件,SMS,Web通知和任何其他类型的通知

我们将有两种类型的通知:

  • 订阅通知:当另一个用户订阅时发送给用户
  • 帖子通知:发布新帖子时发送给该用户的订阅者。

订阅通知


使用工匠命令,我们可以为通知生成迁移:

php artisan notifications:table

让我们进行迁移并创建此表。

php artisan migrate

我们将从订阅通知开始。让我们运行以下命令来创建通知类:

php artisan make:notification UserFollowed

然后,我们修改刚刚创建的通知类文件:

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,
        ];
    }
}

使用这几行代码,我们已经可以实现很多目标。首先,我们要求在$follower生成此通知时实现实例

使用该方法via,我们告诉Laravel通过通道发送此通知database。当Laravel遇到此问题时,它将在通知表中创建一个新条目。

user_idtype通知被自动设置,再加上我们能够扩大与其他数据的通知。这就是它的用途toDatabase。返回的数组将添加到data通知字段

最后,感谢实施ShouldQueue,Laravel会自动将此通知放置在后台运行的队列中,这将加快响应速度。这是有道理的,因为稍后使用Pusher时将添加HTTP调用。

让我们实现一个用户订阅通知。

// ...
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);

            //  ,   
            $user->notify(new UserFollowed($follower));

            return back()->withSuccess("You are now friends with {$user->name}");
        }

        return back()->withSuccess("You are already following {$user->name}");
    }

    //...
}

我们可以为User模型调用notify方法,因为它已经使用了Notifiable特征。

您要通知的任何模型都应使用它来访问notify方法。

我们将通知标记为已读

通知将包含一些信息和资源链接。例如:当用户收到有关新消息的通知时,该通知应包含内容丰富的文本,在按下该消息时将用户重定向到该消息并标记为已读。

我们将创建一个图层,该图层将检查请求中是否存在事件?read=notification_id并将其标记为已读。

让我们使用以下命令创建该层:

php artisan make:middleware MarkNotificationAsRead

然后,将这段代码放在handleinterlayer 方法中

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

为了针对每个请求执行我们的图层,我们将其添加到中$middlewareGroups

//...
class Kernel extends HttpKernel
{
    //...
    protected $middlewareGroups = [
        'web' => [
            //...
            \App\Http\Middleware\MarkNotificationAsRead::class,
        ],
        // ...
    ];
    //...
}

之后,让我们显示通知。

显示通知


我们需要使用AJAX显示通知列表,然后使用Pusher实时更新通知列表。首先,让我们notifications为控制器添加一个方法

// ...
class UsersController extends Controller
{
    // ...
    public function notifications()
    {
        return auth()->user()->unreadNotifications()->limit(5)->get()->toArray();
    }
}

此代码将返回最后5条未读的通知。我们只需要添加一条路由即可访问它。

//...
Route::group([ 'middleware' => 'auth' ], function () {
    // ...
    Route::get('/notifications', 'UsersController@notifications');
});

现在,在标题中添加一个用于通知的下拉列表。

<head>
    <!-- // ... // -->
    <!-- Scripts -->
    <script>
        window.Laravel = <?php echo json_encode([
            'csrfToken' => csrf_token(),
        ]); ?>
    </script>
    <!--   id     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>
<!-- // ... // -->

我们还向window.Laravel.userId脚本添加了全局变量以获取当前用户ID。

JavaScript和SASS


我们将使用Laravel Mix来编译JavaScript和SASS。首先,我们需要安装npm软件包。

npm install

现在让我们将此代码添加到app.js

window._ = require('lodash');
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
var notifications = [];
const NOTIFICATION_TYPES = {
    follow: 'App\\Notifications\\UserFollowed'
};

这只是初始化。我们将使用通知来存储所有通知对象,无论它们是通过AJAX还是Pusher检索的。

您可能已经猜到了,它NOTIFICATION_TYPES包含通知类型。

现在,让我们通过AJAX获得(“ GET”)通知。

//...
$(document).ready(function() {
    // ,      
    if(Laravel.userId) {
        $.get('/notifications', function (data) {
            addNotifications(data, "#notifications");
        });
    }
});
function addNotifications(newNotifications, target) {
    notifications = _.concat(notifications, newNotifications);
    //    5 
    notifications.slice(0, 5);
    showNotifications(notifications, target);
}

由于有了此代码,我们从API收到了最新的通知,并将它们放在了一个下拉列表中。

在内部,addNotifications我们使用Lodash将现有通知与新通知结合在一起,并只取最后5条,这将显示出来。

我们还需要一些其他功能来完成这项工作。

//...
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');
    }
}

此功能创建一行所有通知,并将其放置在下拉列表中。
如果未收到任何通知,则仅显示“无通知”。

它还向下拉按钮添加了一个类,该类仅在有通知时更改颜色。有点像Github通知。

最后,一些帮助程序功能用于创建通知字符串。

//...
//   
function makeNotification(notification) {
    var to = routeNotification(notification);
    var notificationText = makeNotificationText(notification);
    return '<li><a href="' + to + '">' + notificationText + '</a></li>';
}
//        
function routeNotification(notification) {
    var to = '?read=' + notification.id;
    if(notification.type === NOTIFICATION_TYPES.follow) {
        to = 'users' + 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';
    }
    return text;
}

现在,我们将其添加到我们的文件中app.scss

//... 
#notifications.has-notifications {
  color: #bf5329
}

让我们编译资产:

npm run dev

现在,如果您尝试订阅用户,则该用户将收到通知。当他单击它时,他将被重定向到/users,通知本身也消失了。

新帖通知


当用户发布新帖子时,我们将通知订阅者。

让我们从创建一个通知类开始。

php artisan make:notification NewPost

让我们修改生成的类,如下所示:

// ..
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,
        ];
    }
}

接下来,我们需要发送通知。有几种方法可以做到这一点。

我喜欢使用雄辩的观察者。

让我们为Post创建一个观察者,并监听其事件。我们将创建一个新类: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));
        }
    }
}

然后在AppServiceProvider以下位置注册观察者

//...
use App\Observers\PostObserver;
use App\Post;
class AppServiceProvider extends ServiceProvider
{
    //...
    public function boot()
    {
        Post::observe(PostObserver::class);
    }
    //...
}

现在我们只需要格式化消息以在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;
}

瞧!通知用户订阅和新帖子!自己尝试!

使用Pusher实时退出


是时候使用Pusher通过Web套接字接收实时通知了。

pusher.com上注册免费的Pusher帐户并创建一个新应用。

...
BROADCAST_DRIVER=pusher
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=

在配置文件中为您的帐户设置参数broadcasting

//...
    'connections' => [
            'pusher' => [
                //...
                'options' => [
                    'cluster' => 'eu',
                    'encrypted' => true
                ],
            ],
    //...

然后我们将App\Providers\BroadcastServiceProvider在数组中注册providers

// ...
'providers' => [
    // ...
    App\Providers\BroadcastServiceProvider
    //...
],
//...

现在我们需要从Pu​​sher安装PHP SDK和Laravel Echo:

composer require pusher/pusher-php-server
npm install --save laravel-echo pusher-js

我们需要设置广播通知数据。让我们修改通知UserFollowed

//...
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,
            ],
        ];
    }
}

NewPost

//...
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,
            ],
        ];
    }
}

我们需要做的最后一件事是更新我们的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');
            });
    }
});

仅此而已。通知是实时添加的。现在,您可以使用该应用程序,并查看通知的更新方式。

结论


Pusher具有非常人性化的API,它使实时事件的实现变得异常简单。结合Laravel通知,我们可以从一个地方通过多个渠道(电子邮件,SMS,Slack等)发送通知。在本指南中,我们添加了在一个简单博客中跟踪用户活动的功能,并使用上述工具对其进行了改进,以获得一些流畅的实时功能。

Pusher和Laravel具有更多通知:串联服务可让您将发布/订阅消息实时发送到浏览器,移动电话和IOT设备。还有一个用于获取在线/离线用户状态的API。

请参阅他们的文档(Pusher文档Pusher教程Laravel文档),以了解有关其使用和真正潜力的更多信息。

如果您有任何意见,问题或建议,请随时在下面的评论中分享!



了解有关该课程的更多信息。



All Articles