Comment ajouter des notifications en temps réel à Laravel à l'aide de Pusher

Une traduction de l'article a été préparée spécialement pour les étudiants du cours «Laravel» .




Un internaute moderne s'attend à être informé de tout ce qui se passe dans l'application. Vous ne voudriez pas être le site Web qui n'a même pas de liste déroulante de notifications, qui peut maintenant être trouvée non seulement sur tous les sites de réseaux sociaux, mais généralement partout de nos jours.

Heureusement, avec Laravel et Pusher, l' implémentation de cette fonctionnalité est assez simple.

Notifications en temps réel


Afin de fournir une expérience utilisateur positive, les notifications doivent être affichées en temps réel. Une approche consiste à envoyer régulièrement une demande AJAX au serveur et à recevoir les dernières notifications, le cas échéant.

La meilleure approche consiste à utiliser les capacités de WebSockets et à recevoir des notifications lors de leur envoi. C'est exactement ce que nous allons mettre en œuvre dans cet article.

Pousseur


Pusher est un service Web permettant d'intégrer des fonctionnalités bidirectionnelles en temps réel via WebSockets dans des applications Web et mobiles.

Il a une API très simple, mais nous allons rendre son utilisation encore plus facile avec Laravel Broadcasting et Laravel Echo.

Dans cet article, nous allons ajouter des notifications en temps réel à un blog existant.

Projet


Initialisation


Tout d'abord, nous clonons un simple blog Laravel:

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

Ensuite, nous allons créer la base de données MySQL et configurer les variables d'environnement pour donner à l'application l'accès à la base de données.

Copions et mettons env.exampleà .envjour les variables associées à la base de données.

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

Maintenant installons les dépendances du projet en utilisant

composer install

Et exécutez la commande migration and populate pour remplir la base de données avec des données:

php artisan migrate --seed

Si vous exécutez l'application et accédez à /posts, vous pouvez voir une liste des publications générées.

Vérifiez l'application, enregistrez l'utilisateur et créez des messages. Il s'agit d'une application très simple, mais elle est idéale pour la démonstration.

Abonnez-vous aux utilisateurs


Nous souhaitons donner aux utilisateurs la possibilité de s'abonner les uns aux autres, nous devons donc créer une relation plusieurs-à-plusieurs entre les utilisateurs afin de réaliser cela.

Créons un tableau croisé dynamique qui relie les utilisateurs aux utilisateurs. Faisons une nouvelle migration followers:

php artisan make:migration create_followers_table --create=followers

Nous devons ajouter plusieurs champs à cette migration: user_idpour représenter l'utilisateur auquel vous follows_idêtes abonné , et un champ pour représenter l'utilisateur auquel ils sont abonnés.

Mettez Ă  jour la migration comme suit:

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

Passons maintenant à la création de la table:

php artisan migrate

Ajoutons des méthodes de relation au modèle 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();
    }
}

Maintenant que le modèle Usera les relations nécessaires, il followersrenvoie tous les abonnés de l'utilisateur et followsrenvoie tous ceux auxquels l'utilisateur est abonné.

Nous aurons besoin de quelques fonctions auxiliaires qui permettent à l'utilisateur de s'abonner à d'autres utilisateurs - follow, et de vérifier si l'utilisateur est abonné à un utilisateur spécifique - 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']);
    }

}

Bien. Après avoir préparé le modèle, vous devez faire une liste d'utilisateurs.

une liste d'utilisateurs


Commençons par identifier les itinéraires nécessaires.

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

Il est alors temps de créer un nouveau contrôleur pour les utilisateurs:

php artisan make:controller UsersController

Nous y ajouterons une méthode 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'));
    }
}

La méthode doit être introduite. Créons une vue users.indexet y mettons le balisage suivant:

@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

Vous pouvez maintenant visiter la page /userspour voir la liste des utilisateurs.

Suivre et se désabonner


Dans UsersControllerles méthodes manquantes followet unfollow. Implémentons-les pour terminer cette partie.

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

Nous avons terminé avec cette fonctionnalité. Maintenant, nous pouvons nous abonner aux utilisateurs et nous désinscrire de ceux-ci sur la page /users.

Notifications


Laravel fournit une API pour envoyer des notifications sur plusieurs canaux. Les e-mails, SMS, notifications Web et tout autre type de notifications peuvent être envoyés à l'aide de la classe Notification .

Nous aurons deux types de notifications:

  • Notification d'abonnement: envoyĂ©e Ă  l'utilisateur lorsqu'un autre utilisateur s'y abonne
  • Notification de publication: envoyĂ©e aux abonnĂ©s de cet utilisateur lorsqu'une nouvelle publication est publiĂ©e.

Notification d'abonnement


En utilisant des commandes artisanales, nous pouvons générer une migration pour les notifications:

php artisan notifications:table

Faisons la migration et créons cette table.

php artisan migrate

Nous commencerons par les notifications d'abonnement. Exécutons cette commande pour créer une classe de notification:

php artisan make:notification UserFollowed

Ensuite, nous modifions le fichier de classe de notification que nous venons de créer:

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

Avec ces quelques lignes de code, nous pouvons déjà faire beaucoup. Tout d'abord, nous exigeons que l'instance $followersoit implémentée lors de la génération de cette notification.

En utilisant la méthode via, nous demandons à Laravel d'envoyer cette notification via le canal database. Lorsque Laravel rencontre cela, il crée une nouvelle entrée dans la table de notification.

user_idet les typenotifications sont définies automatiquement, et nous pouvons étendre la notification avec des données supplémentaires. C'est pour ça toDatabase. Le tableau retourné sera ajouté au champ de datanotification.

Et enfin, grâce à l'implémentationShouldQueue, Laravel placera automatiquement cette notification dans une file d'attente qui s'exécutera en arrière-plan, ce qui accélérera la réponse. Cela a du sens car nous ajouterons des appels HTTP lorsque nous utiliserons Pusher plus tard.

Implémentons une notification d'abonnement utilisateur.

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

    //...
}

Nous pouvons appeler la méthode notify pour le modèle User car elle utilise déjà le trait Notifiable.

Tout modèle que vous souhaitez notifier doit l'utiliser pour accéder à la méthode de notification.

Nous marquons la notification comme lue Les

notifications contiendront des informations et un lien vers la ressource. Par exemple: lorsque l'utilisateur reçoit une notification concernant un nouveau message, la notification doit contenir un texte informatif, rediriger l'utilisateur vers le message lorsqu'il est pressé et être marqué comme lu.

Nous allons créer une couche qui vérifiera s'il y a une occurrence dans la demande ?read=notification_idet la marquera comme lue.

Faisons ce calque avec la commande suivante:

php artisan make:middleware MarkNotificationAsRead

Ensuite, mettons ce code dans la méthode handleintercouche:

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

Afin que notre couche soit exécutée pour chaque demande, nous l'ajouterons à $middlewareGroups.

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

Après cela, nous allons afficher les notifications.

Afficher les notifications


Nous devons afficher la liste des notifications à l'aide d'AJAX, puis la mettre à jour en temps réel à l'aide de Pusher. Tout d'abord, ajoutons une méthode notificationsau contrôleur:

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

Ce code renverra les 5 dernières notifications non lues. Nous avons juste besoin d'ajouter un itinéraire pour le rendre accessible.

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

Ajoutez maintenant une liste déroulante pour les notifications dans l'en-tête.

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

Nous avons également ajouté une variable globale window.Laravel.userIdau script pour obtenir l'ID utilisateur actuel.

JavaScript et SASS


Nous allons utiliser Laravel Mix pour compiler JavaScript et SASS. Tout d'abord, nous devons installer les packages npm.

npm install

Ajoutons maintenant ce code Ă  app.js:

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

Ce n'est qu'une initialisation. Nous allons utiliser les notifications pour stocker tous les objets de notification, qu'ils soient récupérés via AJAX ou Pusher.

Comme vous l'avez probablement déjà deviné, il NOTIFICATION_TYPEScontient des types de notification.

Maintenant, obtenons des notifications («GET») 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);
}

Grâce à ce code, nous recevons les dernières notifications de notre API et les mettons dans une liste déroulante.

À l'intérieur, addNotificationsnous combinons les notifications existantes avec de nouvelles en utilisant Lodash , et prenons uniquement les 5 dernières, qui seront affichées.

Nous avons besoin de quelques fonctions supplémentaires pour terminer le travail.

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

Cette fonction crée une ligne de toutes les notifications et la place dans la liste déroulante.
Si aucune notification n'a été reçue, simplement «Aucune notification» s'affiche.

Il ajoute également une classe au bouton déroulant, qui change simplement de couleur lorsqu'il y a des notifications. Un peu comme les notifications Github.

Enfin, certaines fonctions d'assistance pour créer des chaînes de notification.

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

Maintenant, nous ajoutons simplement ceci Ă  notre fichier app.scss:

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

Compilons les actifs:

npm run dev

Maintenant, si vous essayez de vous abonner à un utilisateur, il recevra une notification. Lorsqu'il clique dessus, il sera redirigé vers /userset la notification elle-même disparaît.

Nouvelle notification de publication


Nous allons informer les abonnés lorsqu'un utilisateur publie un nouveau message.

Commençons par créer une classe de notification.

php artisan make:notification NewPost

Modifions la classe générée comme suit:

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

Ensuite, nous devons envoyer une notification. Il y a plusieurs moyens de le faire.

J'aime utiliser les observateurs Eloquent.

Créons un observateur pour le Post et écoutons ses événements. Nous allons créer une nouvelle 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));
        }
    }
}

Enregistrez ensuite l'observateur dans AppServiceProvider:

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

Il ne nous reste plus qu'Ă  formater le message pour l'afficher en 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;
}

Et le tour est joué! Les utilisateurs sont informés des abonnements et des nouveaux messages! Essayez-le vous-même!

Sortir en temps réel avec Pusher


Il est temps d'utiliser Pusher pour recevoir des notifications en temps réel via des sockets Web.

Inscrivez-vous pour un compte Pusher gratuit sur pusher.com et créez une nouvelle application.

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

Définissez les paramètres de votre compte dans le fichier de configuration broadcasting:

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

Ensuite, nous nous enregistrerons App\Providers\BroadcastServiceProviderdans le tableau providers.

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

Maintenant, nous devons installer le SDK PHP et Laravel Echo de Pusher:

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

Nous devons configurer des données de notification pour la diffusion. Modifions la notification 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,
            ],
        ];
    }
}

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

La dernière chose que nous devons faire est de mettre à jour notre JS. Ouvrez app.jset ajoutez le code suivant

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

Et c'est tout. Les notifications sont ajoutées en temps réel. Vous pouvez maintenant jouer avec l'application et voir comment les notifications sont mises à jour.

Conclusion


Pusher possède une API très conviviale qui rend la mise en œuvre des événements en temps réel incroyablement simple. En combinaison avec les notifications Laravel, nous pouvons envoyer des notifications via plusieurs canaux (e-mail, SMS, Slack, etc.) à partir d'un seul endroit. Dans ce guide, nous avons ajouté des fonctionnalités pour suivre l'activité des utilisateurs dans un blog simple et l'avons améliorée à l'aide des outils ci-dessus pour obtenir des fonctionnalités en temps réel fluides.

Pusher et Laravel ont beaucoup plus de notifications: en tandem, les services vous permettent d'envoyer des messages pub / sub en temps réel aux navigateurs, téléphones mobiles et appareils IOT. Il existe également une API pour obtenir le statut d'utilisateur en ligne / hors ligne.

Veuillez consulter leur documentation ( Pusher docs, Didacticiels Pusher , documents Laravel ) pour en savoir plus sur leur utilisation et leur véritable potentiel.

Si vous avez des commentaires, des questions ou des recommandations, n'hésitez pas à les partager dans les commentaires ci-dessous!



En savoir plus sur le cours.



All Articles