Como adicionar notificações em tempo real ao Laravel usando o Pusher

Uma tradução do artigo foi preparada especialmente para os alunos do curso “Framework Laravel” .




Um usuário moderno da web espera ser informado sobre tudo o que acontece no aplicativo. Você não gostaria de ser aquele site que nem sequer tem uma lista suspensa de notificações, que agora podem ser encontradas não apenas em todos os sites de redes sociais, mas geralmente em todos os lugares hoje em dia.

Felizmente, com o Laravel e o Pusher, a implementação dessa funcionalidade é bastante simples.

Notificações em tempo real


Para proporcionar uma experiência positiva ao usuário, as notificações devem ser exibidas em tempo real. Uma abordagem é enviar regularmente uma solicitação AJAX ao servidor e receber as notificações mais recentes, se houver.

A melhor abordagem é usar os recursos do WebSockets e receber notificações quando forem enviadas. É exatamente isso que vamos implementar neste artigo.

Empurrador


O Pusher é um serviço da Web para integrar funcionalidade bidirecional em tempo real através do WebSockets a aplicativos da Web e móveis.

Ele tem uma API muito simples, mas vamos facilitar ainda mais o uso com o Laravel Broadcasting e o Laravel Echo.

Neste artigo, adicionaremos notificações em tempo real a um blog existente.

Projeto


Inicialização


Primeiro, clonamos um blog simples do Laravel:

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

Em seguida, criaremos o banco de dados MySQL e configuraremos as variáveis ​​de ambiente para fornecer ao aplicativo acesso ao banco de dados.

Vamos cópia env.exampleem .enve atualizar as variáveis associadas com o banco de dados.

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

Agora vamos instalar as dependências do projeto usando

composer install

E execute o comando de migração e preencher para preencher o banco de dados com alguns dados:

php artisan migrate --seed

Se você executar o aplicativo e acessar /posts, poderá ver uma lista de postagens geradas.

Verifique o aplicativo, registre o usuário e crie algumas mensagens. Esta é uma aplicação muito simples, mas é ótima para demonstração.

Inscrever-se em Usuários


Gostaríamos de dar aos usuários a oportunidade de se inscreverem, portanto, devemos criar um relacionamento de muitos para muitos para que isso ocorra.

Vamos criar uma tabela dinâmica que vincula usuários a usuários. Vamos fazer uma nova migração followers:

php artisan make:migration create_followers_table --create=followers

Precisamos adicionar vários campos a essa migração: user_idpara representar o usuário que está inscrito e um campo follows_idpara representar o usuário ao qual ele está inscrito.

Atualize a migração da seguinte maneira:

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

Agora, vamos criar a tabela:

php artisan migrate

Vamos adicionar métodos de relacionamento ao modelo 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();
    }
}

Agora que o modelo Userpossui os relacionamentos necessários, ele followersretorna todos os assinantes do usuário e followstodos aqueles em que o usuário está inscrito.

Vamos precisar de algumas funções auxiliares que permitam ao usuário assinar outros usuários - followe verificar se o usuário está inscrito em um usuário específico - 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']);
    }

}

Bem. Depois de preparar o modelo, você precisa fazer uma lista de usuários.

uma lista de usuários


Vamos começar identificando as rotas necessárias.

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

Então é hora de criar um novo controlador para os usuários:

php artisan make:controller UsersController

Vamos adicionar um método a ele 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'));
    }
}

O método precisa ser introduzido. Vamos criar uma visão users.indexe colocar a seguinte marcação nela:

@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

Agora você pode visitar a página /userspara ver a lista de usuários.

Seguir e deixar de seguir


Em UsersControllermétodos ausentes followe unfollow. Vamos implementá-los para concluir esta parte.

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

Terminamos com essa funcionalidade. Agora podemos assinar usuários e cancelar a assinatura deles na página /users.

Notificações


O Laravel fornece uma API para enviar notificações por vários canais. E-mail, SMS, notificações da web e qualquer outro tipo de notificação podem ser enviados usando a classe Notification .

Teremos dois tipos de notificações:

  • Notificação de assinatura: enviada ao usuário quando outro usuário assina
  • Notificação de postagem: enviada aos assinantes deste usuário quando uma nova postagem é publicada.

Notificação de Assinatura


Usando comandos artesanais, podemos gerar uma migração para notificações:

php artisan notifications:table

Vamos fazer a migração e criar esta tabela.

php artisan migrate

Começaremos com notificações de inscrição. Vamos executar este comando para criar uma classe de notificação:

php artisan make:notification UserFollowed

Em seguida, modificamos o arquivo de classe de notificação que acabamos de criar:

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

Com essas poucas linhas de código, já podemos conseguir muito. Primeiro, exigimos que a instância $followerseja implementada quando essa notificação for gerada.

Usando o método via, dizemos ao Laravel para enviar esta notificação através do canal database. Quando o Laravel encontra isso, ele cria uma nova entrada na tabela de notificação.

user_ide as typenotificações são definidas automaticamente. Além disso, podemos expandir a notificação com dados adicionais. É para isso que serve toDatabase. A matriz retornada será adicionada ao campo de datanotificação.

E, finalmente, graças à implementaçãoShouldQueue, O Laravel colocará automaticamente essa notificação em uma fila que será executada em segundo plano, o que acelerará a resposta. Isso faz sentido, porque adicionaremos chamadas HTTP quando usarmos o Pusher posteriormente.

Vamos implementar uma notificação de assinatura do usuário.

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

    //...
}

Podemos chamar o método de notificação para o modelo de usuário porque ele já usa a característica de notificação.

Qualquer modelo que você deseja notificar deve usá-lo para obter acesso ao método de notificação.

Marcamos a notificação como lida. As

notificações conterão algumas informações e um link para o recurso. Por exemplo: quando o usuário recebe uma notificação sobre uma nova mensagem, ela deve conter texto informativo, redirecionar o usuário para a mensagem quando pressionada e ser marcada como lida.

Vamos criar uma camada que verificará se há uma ocorrência na solicitação ?read=notification_ide a marcará como lida.

Vamos fazer essa camada com o seguinte comando:

php artisan make:middleware MarkNotificationAsRead

Então vamos colocar esse código no método handleintercalar:

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

Para que nossa camada seja executada para cada solicitação, nós a adicionaremos $middlewareGroups.

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

Depois disso, vamos exibir as notificações.

Exibir notificações


Precisamos mostrar a lista de notificações usando AJAX e atualizá-la em tempo real usando o Pusher. Primeiro, vamos adicionar um método notificationsao controlador:

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

Este código retornará as últimas 5 notificações não lidas. Só precisamos adicionar uma rota para torná-la acessível.

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

Agora adicione uma lista suspensa para notificações no cabeçalho.

<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>
<!-- // ... // -->

Também adicionamos uma variável global window.Laravel.userIdao script para obter o ID do usuário atual.

JavaScript e SASS


Vamos usar o Laravel Mix para compilar JavaScript e SASS. Primeiro, precisamos instalar os pacotes npm.

npm install

Agora vamos adicionar este código a app.js:

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

Isso é apenas inicialização. Vamos usar as notificações para armazenar todos os objetos de notificação, sejam eles recuperados pelo AJAX ou Pusher.

Como você provavelmente já adivinhou, ele NOTIFICATION_TYPEScontém tipos de notificação.

Agora vamos receber ("GET") notificações via AJAX.

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

Graças a esse código, recebemos as notificações mais recentes da nossa API e as colocamos em uma lista suspensa.

Por dentro, addNotificationscombinamos as notificações existentes com as novas usando o Lodash e fazemos apenas as últimas 5, que serão mostradas.

Precisamos de mais algumas funções para concluir o trabalho.

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

Essa função cria uma linha de todas as notificações e a coloca na lista suspensa.
Se nenhuma notificação foi recebida, simplesmente “Nenhuma notificação” é exibida.

Ele também adiciona uma classe ao botão suspenso, que simplesmente muda de cor quando há notificações. Um pouco como as notificações do Github.

Por fim, algumas funções auxiliares para criar seqüências de notificação.

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

Agora apenas adicionamos isso ao nosso arquivo app.scss:

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

Vamos compilar os ativos:

npm run dev

Agora, se você tentar se inscrever em um usuário, ele receberá uma notificação. Quando ele clica nele, ele será redirecionado para /users, e a própria notificação desaparece.

Nova notificação de postagem


Vamos notificar os assinantes quando um usuário publica uma nova postagem.

Vamos começar criando uma classe de notificação.

php artisan make:notification NewPost

Vamos modificar a classe gerada da seguinte maneira:

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

Em seguida, precisamos enviar uma notificação. Existem várias maneiras de fazer isso.

Eu gosto de usar observadores eloquentes.

Vamos criar um observador para o Post e ouvir seus eventos. Vamos criar uma nova classe: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));
        }
    }
}

Em seguida, registre o observador em AppServiceProvider:

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

Agora só precisamos formatar a mensagem para exibição em 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;
}

E pronto! Os usuários são notificados de assinaturas e novas postagens! Tente você mesmo!

Saia em tempo real com o Pusher


É hora de usar o Pusher para receber notificações em tempo real via soquetes da web.

Registre-se para obter uma conta gratuita do Pusher em pusher.com e crie um novo aplicativo.

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

Defina os parâmetros para sua conta no arquivo de configuração broadcasting:

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

Então, vamos registrar App\Providers\BroadcastServiceProviderna matriz providers.

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

Agora precisamos instalar o PHP SDK e o Laravel Echo do Pusher:

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

Precisamos configurar dados de notificação para transmissão. Vamos modificar a notificação 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,
            ],
        ];
    }
}

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

A última coisa que precisamos fazer é atualizar nosso JS. Abra app.jse adicione o seguinte código

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

E isso é tudo. As notificações são adicionadas em tempo real. Agora você pode jogar com o aplicativo e ver como as notificações são atualizadas.

Conclusão


O Pusher possui uma API muito amigável que torna a implementação de eventos em tempo real incrivelmente simples. Em combinação com as notificações do Laravel, podemos enviar notificações através de vários canais (email, SMS, Slack, etc.) a partir de um único local. Neste guia, adicionamos funcionalidade para rastrear a atividade do usuário em um blog simples e a aprimoramos usando as ferramentas acima para obter uma funcionalidade suave em tempo real.

Pusher e Laravel têm muito mais notificações: em conjunto, os serviços permitem enviar mensagens de pub / sub em tempo real para navegadores, telefones celulares e dispositivos IOT. Há também uma API para obter o status do usuário online / offline.

Consulte a documentação deles (documentos Pusher, Tutoriais Pusher , documentos do Laravel ) para saber mais sobre o uso e o verdadeiro potencial deles.

Se você tiver quaisquer comentários, perguntas ou recomendações, compartilhe-os nos comentários abaixo!



Saiba mais sobre o curso.



All Articles