Dans ce tutoriel, on va s'appuyer sur un outil puissant de Laravel pour notifier facilement nos utilisateurs via websocket.
Le contexte
Sur notre site d’e-commerce nous allons mettre en place une notification lors de la livraison d’une commande à un utilisateur.
Avant de s’attaquer au cœur du sujet de notre tutoriel, on va créer les briques nécessaires à son élaboration.
On va tout d’abord créer notre modèle Order
qui est lié au modèle User
avec une relation belongsTo
ce qui signifie qu’une commande est associé à un seul utilisateur, voici sa classe :
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Factories\HasFactory; 6use Illuminate\Database\Eloquent\Model; 7 8class Order extends Model 9{10 use HasFactory;11 12 protected $fillable = [13 'name', 'amount',14 ];15 16 public function user()17 {18 return $this->belongsTo(User::class);19 }20}
Et la migration qui définit sa structure en base :
1<?php 2 3use Illuminate\Database\Migrations\Migration; 4use Illuminate\Database\Schema\Blueprint; 5use Illuminate\Support\Facades\Schema; 6 7return new class extends Migration 8{ 9 public function up()10 {11 Schema::create('orders', function (Blueprint $table) {12 $table->id();13 $table->timestamps();14 $table->string('name');15 $table->double('amount');16 $table->foreignIdFor(\App\Models\User::class);17 });18 }19 20 public function down()21 {22 Schema::dropIfExists('orders');23 }24};
On établit aussi la relation inverse dans le modèle User
:
1<?php 2 3namespace App\Models; 4 5use Illuminate\Contracts\Auth\MustVerifyEmail; 6use Illuminate\Database\Eloquent\Factories\HasFactory; 7use Illuminate\Foundation\Auth\User as Authenticatable; 8use Illuminate\Notifications\Notifiable; 9 10class User extends Authenticatable11{12 // ...13 14 public function orders()15 {16 return $this->hasMany(Order::class);17 }18}
Ensuite, nous allons créer John, notre aimable client qui est ravi de nous aider dans notre périple, en utilisant la commande php artisan tinker
:
Enfin, on crée une route qui nous permet de générer une commande et d'afficher son nom en sortie, celle-ci est assignée à notre super client John :
1Route::get('/orders/save', function () { 2 $user = \App\Models\User::where('name', 'John')->first(); 3 4 $order = \App\Models\Order::make([ 5 'name' => \Illuminate\Support\Str::random(8), 6 'amount' => 19.89, 7 ]); 8 9 $user->orders()->save($order);10 11 echo $order->name;12});
Création de la notification
Le système de notification de Laravel est très complet, on va s’appuyer sur ce dernier pour faciliter grandement la mise en place de notre fonctionnalité.
On commence par créer notre nouvelle classe App\Notifications\OrderDelivered
:
1<?php2 3namespace App\Notifications;4 5use Illuminate\Notifications\Notification;6 7class OrderDelivered extends Notification8{9}
On y ajoute un constructeur qui a pour seul paramètre la commande dont la livraison a lieu :
1public function __construct(public Order $order) {}
Une notification peut-être envoyée via un ou plusieurs canaux déterminés au sein de la méthode via
. Pour nos besoins, on choisit le canal broadcast
nécessaire pour le websocket :
1public function via($notifiable): array2{3 return ['broadcast'];4}
A noter, le paramètre $notifiable
qui est l’instance de l’utilisateur cible de la notification, ce dernier peut servir pour une sélection particulière de canaux, dans l'éventualité où on a implémenter la logique.
Pour chacun des canaux, il est nécessaire d’implémenter la méthode correspondante qui sert à la préparation des données spécifiques de celui-ci. Dans notre cas, on implémente toBroadcast
qui doit renvoyer un objet de type Illuminate\Notifications\Messages\BroadcastMessage
:
1public function toBroadcast($notifiable)2{3 return new BroadcastMessage([4 'order' => $this->order,5 ]);6}
A noter, encore une fois, que notre utilisateur est disponible via $notifiable
et par conséquent, on pourrait envisager un traitement particulier des données en fonction de ce dernier.
Et voilà, notre notification est prête, reste à l’utiliser.
Déclencher l’envoi
Maintenant, on va s’attarder à provoquer l’envoi de la notification. Pour cela, on déclare une route qui simulera la livraison de notre commande et qu’il est temps de notifier son propriétaire :
1Route::get('/orders/deliver/{order:name}', function (\App\Models\Order $order) {2 $user = $order->user;3 4 $user->notify(new \App\Notifications\OrderDelivered($order));5 6 echo 'Notification émise à ' . $user->name;7});
Comme on peut le voir, on dispose d'une méthode User::notify()
disponible part le trait Illuminate\Notifications\Notifiable
qui accepte en paramètre une notification. On aurait tout à fait pu utiliser la façade Notification
à la place.
Ensuite, dans le cadre de ce tutoriel on utilise le service Pusher pour la propagation des notifications via websocket, mais il est tout à fait possible d’utiliser son propre serveur.
Il nous ai donc nécessaire d’intégrer le paquet composer dédié :
1composer require pusher/pusher-php-server
De créer un compte sur le site de Pusher et de renseigner les variables d'environnement suivantes :
1PUSHER_APP_ID=your-pusher-app-id2PUSHER_APP_KEY=your-pusher-key3PUSHER_APP_SECRET=your-pusher-secret4PUSHER_APP_CLUSTER=mt15 6BROADCAST_DRIVER=pusher
Réception côté client
On commence par la configuration de notre client Javascript pour qu’il puisse écouter via websocket.
On installe donc la librairie Pusher et Laravel Echo :
1npm install --save-dev laravel-echo pusher-js
Ensuite on décommente les lignes suivantes dans le fichier resources\js\bootstrap.js
:
1import Echo from 'laravel-echo'; 2 3window.Pusher = require('pusher-js'); 4 5window.Echo = new Echo({ 6 broadcaster: 'pusher', 7 key: process.env.MIX_PUSHER_APP_KEY, 8 cluster: process.env.MIX_PUSHER_APP_CLUSTER, 9 forceTLS: true10});
Maintenant que la configuration est terminée, on va pouvoir créer un composant VueJs (v3) qui va s’abonner au canal privé de l’utilisateur et afficher le contenu de la notification dans une liste :
1<template> 2 <div> 3 <h1>Notifications</h1> 4 <ul> 5 <li v-for="notification in notifications"> 6 <div>La commande {{ notification.order.name }} d'un montant de {{ notification.order.amount }} a été livrée.</div> 7 </li> 8 </ul> 9 </div>10</template>11 12<script>13export default {14 name: "Notifications",15 16 props: [17 'user',18 ],19 20 data() {21 return {22 notifications: [],23 }24 },25 26 created() {27 Echo.private('App.Models.User.' + this.user)28 .notification((notification) => {29 this.notifications.unshift(notification);30 });31 }32}33</script>
La logique intéressante réside dans la méthode created()
, où on demande à notre client JS d'écouter sur le canal privé de l'utilisateur fourni dans les props
.
Ensuite on ajoute notre composant à l’initialisation de VueJs dans le fichier resources/js/app.js
:
1require('./bootstrap'); 2 3import { createApp } from "vue"; 4import Notifications from "./components/Notifications"; 5 6const app = createApp({ 7 components: { 8 Notifications, 9 }10});11 12app.mount("#app");
A cet instant, il est nécessaire de recompiler les librairies Javascript
Il nous reste à intégrer notre composant dans une vue, qu’on appel tuto
, et de lui passer l’id de l’utilisateur connecté :
1<div id="app">2 <notifications user="{{ Auth::user()->id }}"></notifications>3</div>4<script src="{{ asset('js/app.js') }}" defer></script>
Puis à définir la route pour y accéder :
1Route::get('/tuto', function () {2 Auth::login(User::where('name', 'John')->first());3 4 return view('tuto');5});
Petit point sécurité : comme on veut transmettre la notification a un utilisateur particulier, il est nécessaire qu’il soit authentifié car Laravel va passer par un canal privé qui lui est dédié et va donc vérifier son identité lors de l’appel côté client. Le Auth::login()
ici présent n'est là que pour les besoins du tuto, à ne pas reproduire en l'état !
Et voilà, on peut dorénavant créer des commandes sur /orders/save
, récupérer son nom et provoquer la notification sur /orders/deliver/xxxxxx
pour enfin consulter les notifications sur le /tuto
!
Pour aller plus loin
Afin de bonifier notre fonctionnalité on peut stocker les notifications en base pour permettre à nos utilisateurs de les consulter ultérieurement et pouvoir les marquer en lu.
Aussi, on peut créer un autre composant qui affichera une petite cloche avec une pastille lorsque des nouvelles notifications seront disponibles pour nos utilisateurs !
A lire
Autres articles de la même catégorie
Améliorer ses requêtes en base de données
L'ORM de Laravel est un outil remarquable. Mais tout n'est pas forcément dans la doc : connaissiez-vous ces astuces ?
Laravel Jutsu
Utilisez Illuminate en dehors de Laravel
Vous les utilisez de façon naturelle sans même vous en rendre compte, les bibliothèques "Illuminate" nous rendent bien des services.
Antoine Benevaut
Tour d'horizon des façades
Présentation du fonctionnement des façades, leurs avantages et inconvénients. Pourquoi faut-il les considérer dans un écosystème Laravel ?
Marc COLLET