On a vu dans ce cours comment envoyer un email avec Laravel. Mais on dispose aussi d’un système complet de notifications, par exemple par SMS, qui inclue aussi les emails ou même la base de données.

Classiquement une notification est un message court pour informer un utilisateur qu’il s’est passé quelque chose qui le concerne dans l’application.

Par exemple une donnée sensible a été mise à jour, on envoie un SMS par sécurité en informant l’utilisateur de ce changement et, si ce n’est pas lui qui l’a effectué, il peut alors intervenir.

Evidemment pour tout ce qui n’est pas email ou base de données il faut utiliser un service externe. Il y a un site dédié pour tous les drivers existants et la liste est déjà longue !

L’application d’exemple utilise les notifications pour l’envoi d’email pour :

  • la confirmation de l’adresse email lors de l’enregistrement,
  • le renouvellement du mot de passe.

Elle utilise également les notifications en base de données pour prévenir les rédacteurs que de nouveaux commentaires ont été ajoutés à leurs articles.

Organisation du code

Les notifications se trouvent dans le dossier app/Notifications :

Ce dossier n’existe pas dans l’installation de base de Laravel, il est ajouté lorsqu’on crée la première notification avec Artisan qui dispose à cet effet d’une commande :

La confirmation de l’adresse email

Pour voir comment fonctionnent les notifications on va voir en détail la confirmation de l’adresse email de l’application d’exemple.

Par défaut cette confirmation n’est pas prévue dans Laravel. Du coup lorsque quelqu’un s’enregistre en indiquant une adresse email on n’est pas sûr qu’elle soit valide.

Le contrôleur RegisterController

Par défaut le contrôleur App\Http\Controllers\Auth\RegisterController contient ce code :

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\RegistersUsers;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after login / registration.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
}

Autrement dit toute la logique pour l’enregistrement se fait dans le framework, dans le trait lluminate\Foundation\Auth\RegistersUsers. Pour modifier cette logique on doit surcharger la méthode correspondante :

public function register(Request $request)
{
    $this->validator($request->all())->validate();

    $this->guard()->login($this->create($request->all()));

    return redirect($this->redirectPath());
}

Ici on trouve :

  • la validation,
  • la création de l’utilisateur et sa connexion,
  • une redirection.

On veut ajouter la validation de l’adresse email et évidemment supprimer la connexion automatique. Voici la méthode qui surcharge celle du trait :

public function register(RegisterRequest $request, UserRepository $userRepository)
{
    $user = $userRepository->store(
        $request->all(),
        str_random(30)
    );

    $user->notify(new ConfirmEmail());

    return redirect('/')->with('ok', trans('front/verify.message'));
}

On voit que :

  • la validation se fait maintenant avec une requête de formulaire (ce n’était pas indispensable mais homogène par rapport à toutes les validations de l’application),
  • on envoie une notification (notify) créée par la classe ConfirmEmail,
  • on redirige sur la page d’accueil du site avec un message localisé.

Remarquez qu’on ajoute aux données du formulaire d’enregistrement un code de confirmation (une chaîne aléatoire de 30 caractères).

La partie qui nous intéresse est celle de la notification. Pour que ça fonctionne le modèle doit utiliser le trait Notifiable, ce qui est par défaut le cas du modèle User :

class User extends Authenticatable
{
    use Notifiable;

La notification

Voici la classe App\Notifications\ConfirmEmail :

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;

class ConfirmEmail extends Notification implements ShouldQueue
{
    use Queueable;
    
    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->subject(trans('front/verify.email-title'))
            ->line(trans('front/verify.email-title'))
            ->line(trans('front/verify.email-intro'))
            ->action(trans('front/verify.email-button'), url('confirm/' . $notifiable->confirmation_code));
    }
}

On définit le canal de notification (via), ici mail (ça pourrait être database, broadcast, nexmo ou slack).

La méthode toMail définit le contenu de l’email. Il existe un lot de méthodes pratiques :

  • subject : pour le sujet de l’email,
  • line : pour ajouter une ligne de texte,
  • action : pour ajouter un bouton de lien,

Laravel est équipé d’un système de file d’attente (queue) qui permet de différer les tâches consommatrices de ressources, comme l’envoi d’un email. Ici c’est mis en oeuvre  avec l’interface ShouldQueue et le trait Queueable). Je ne développe pas cet aspect dans ce cours. Vous pouvez trouver la documentation ici.

L’email

Voici l’email résultant en français :

Vous avez une mise en forme automatique !

Par défaut le template est dans le framework. Mais si on doit le modifier, comme c’est le cas pour l’application d’exemple pour la localisation, il existe une commande d’Artisan pour le publier :

php artisan vendor:publish --tag=laravel-notifications

On trouve le template ici :

Il devient ainsi accessible et modifiable.

La notification des rédacteurs pour les nouveaux commentaires

On peut utiliser les notifications en base de données. Elles sont ainsi stockées en attendant d’être lues.

La table des notifications

Pour que ça fonctionne il faut ajouter une table avec cette commande d’Artisan :

php artisan notifications:table

Ce qui a pour effet d’ajouter une migration :

Après avoir effectué la migration on se retrouve avec cette table notifications :

C’est dans cette table que sont stockées les notifications.

La notification

Comme pour les emails il nous faut une classe de notification :

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;
use App\Models\Post;

class Commented extends Notification
{
    /**
     * Post property.
     *
     * @var \App\User\Post
     */
    protected $post;

    /**
     * Create a new notification instance.
     *
     * @param \App\Models\Post
     * @return void
     */
    public function __construct(Post $post)
    {
        $this->post = $post;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['database'];
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            'title' => $this->post->title,
            'slug' => $this->post->slug,
        ];
    }
}

Cette fois dans la méthode via on a database.

On voit qu’on doit transmettre dans le constructeur une instance de l’article concerné ($post). La mise en forme se fait dans la méthode toArray où on retourne un tableau de données qui sera transformé en JSON dans la base.

On va ainsi transmettre le titre et le slug (pour générer l’url) de l’article.

Création d’une notification

Une notification sera créée quand un commentaire le sera. Voici la méthode store du contrôleur des commentaires (App\Http\Controllers\CommentController) :

public function store(CommentRequest $request, BlogRepository $blogRepository)
{
    $this->commentRepository->store($request->all(), $request->user()->id);

    $blog = $blogRepository->getById($request->post_id);
    
    $blog->user->notify(new Commented($blog));
    
    if (!$request->user()->valid) {
        $request->session()->flash('warning', trans('front/blog.warning'));
    }

    return back();
}

La ligne qui crée la notification est celle-ci :

$blog->user->notify(new Commented($blog));

Exactement comme on l’a vu pour les email ci-dessus.

On trouve cet enregistrement dans la table des notifications :

La clé étrangère qui permet de connaître l’utilisateur est notifiable_id.

On a le type de notification pour les distinguer si on en a plusieurs, ce qui n’est pas le cas de l’application d’exemple. La colonne read_at est à NULL parce que la notification n’a pas encore été lue. Dans la colonne data on retrouve sous forme de JSON les informations transmises (title et slug).

Les routes et le contrôleur

On a deux routes :

Route::get('notifications/{user}', 'NotificationController@index');
Route::put('notifications/{notification}', 'NotificationController@update');

La première est pour l’affichage des notifications.

La seconde est pour marquer une notification comme lue.

Voici le contrôleur (App\Http\Controllers\NotificationController) :

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Notifications\DatabaseNotification;

class NotificationController extends Controller
{
    /**
     * Create a new NotificationController instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('redac');
    }

    /**
     * Display a listing notifications.
     * 
     * @param  \App\Models\User $user
     * @return \Illuminate\Http\Response
     */
    public function index(User $user)
    {
        return view('back.notifications.index', compact('user'));
    }

    /**
     * Update the notification.
     *
     * @param  Illuminate\Http\Request $request
     * @param  integer $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, DatabaseNotification $notification)
    {
        $notification->markAsRead();

        if($request->user()->unreadNotifications->isEmpty()) {
            return redirect()->route('blog.index');
        }
 
        return back();
    }
}

On applique le middleware redac pour que les méthodes ne soient accessibles qu’aux rédacteurs.

La première méthode (index) ne pose pas de problème puisqu’on se contente d’utiliser une vue en transmettant une instance du modèle pour l’utilisateur connecté.

La seconde méthode (update) est destinée à marquer une notification comme lue, ce qui se fait avec la méthode markAsRead.

Remarquez que pour les deux méthodes on utilise la liaison automatique entre le paramètre de la route et un modèle. De cette manière on a directement une instance du modèle dans la méthode.

La vue index

Voici la vue pour afficher les notifications (resources/views/back/notifications/index.blade.php) :

@extends('back.template')

@section('main')

  @include('back.partials.entete', ['title' => trans('back/notifications.notifications'), 'icon' => 'bell', 'fil' => trans('back/notifications.new-notifications')])

  <div class="row col-lg-12">
    <div class="table-responsive">
      <table class="table">
        <thead>
          <tr>
            <th>{!! trans('back/notifications.post') !!}</th>
            <th>{!! trans('back/notifications.date') !!}</th>
            <th>{!! trans('back/notifications.valid') !!}</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          @foreach($user->unreadNotifications as $notification)
                <tr>
                  <td>{!! link_to('blog/' . $notification->data['slug'], $notification->data['title']) !!}</td>
                  <td>{{ formatDate($notification->created_at) }}</td> 
                  <td>{!! Form::checkbox(trans('valid'), null, $user->valid, ['disabled' => true]) !!}</td>
                  <td>
                    {!! Form::open(['method' => 'PUT', 'url' => 'notifications/' . $notification->id]) !!}
                      {!! Form::submit(trans('back/notifications.erase'), ['class' => 'btn btn-success btn-block']) !!}
                    {!! Form::close() !!}
                  </td>
                </tr>
          @endforeach
        </tbody>
      </table>
    </div>
  </div>

@endsection

On voit qu’on peut récupérer toutes les notifications non lues de l’utilisateur avec $user->unreadNotifications.

Voici l’aspect de la vue :

Si on clique sur « Effacer » on appelle la méthode update du contrôleur vue ci-dessus et la notification est marquée comme lue (mise à jour de la colonne read_at) et elle disparaît de l’affichage.

Il y aurait encore beaucoup à dire sur les notifications au-delà des deux exemples de ce chapitre, reportez-vous à la documentation officielle pour en savoir plus.

En résumé

  • Laravel comporte un système complet et performant de notifications.
  • On peut envoyer des emails avec les notifications, leur mise en forme est facilitée par la présence de puissantes méthodes et d’un template.
  • On peut stocker les notifications en base de données pour une utilisation ultérieure.

Laisser un commentaire