III. Les notifications▲
On a vu dans ce cours comment envoyer un e-mail avec Laravel. Mais on dispose aussi d'un système complet de notifications, par exemple par SMS, qui inclue aussi les e-mails 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.
Évidemment, pour tout ce qui n'est pas e-mail 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 concernant l'envoi d'e-mail pour :
- la confirmation de l'adresse e-mail lors de l'enregistrement (par l'intermédiaire d'un package) ;
- 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 à leur article.
III-A. 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 :
Évidemment, pour les packages, les notifications se situent dans un dossier du package, par exemple :
III-B. Le renouvellement du mot de passe▲
On a vu en détail le renouvellement du mot de passe dans ce chapitre. J'ai alors précisé qu'on envoyait un e-mail par le système de notification en indiquant que je vous en parlerai plus tard. C'est ce que je vais faire maintenant.
On a vu que Laravel, après la demande de renouvellement de l'utilisateur, expédie ce genre d’e-mail :
Par défaut, la classe de notification se situe dans le framework :
Avec ce code :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
<?php
namespace
Illuminate\Auth\Notifications;
use
Illuminate\Notifications\Notification;
use
Illuminate\Notifications\Messages\MailMessage;
class
ResetPassword extends
Notification
{
/**
* The password reset token.
*
*
@var
string
*/
public
$token
;
/**
* Create a notification instance.
*
*
@param
string
$token
*
@return
void
*/
public
function
__construct
($token
)
{
$this
->
token =
$token
;
}
/**
* Get the notification's channels.
*
*
@param
mixed
$notifiable
*
@return
array|string
*/
public
function
via($notifiable
)
{
return
[
'mail'
];
}
/**
* Build the mail representation of the notification.
*
*
@param
mixed
$notifiable
*
@return
\Illuminate\Notifications\Messages\MailMessage
*/
public
function
toMail($notifiable
)
{
return
(new
MailMessage)
->
line('You are receiving this email because we received a password reset request for your account.'
)
->
action('Reset Password'
,
url(config('app.url'
).
route('password.reset'
,
$this
->
token,
false
)))
->
line('If you did not request a password reset, no further action is required.'
);
}
}
Comme c'est une notification par e-mail, on a une méthode toMail. D'autre part on précise dans la méthode via qu'on retourne un e-mail. La classe MailMessage offre un certain nombre de méthodes bien pratiques comme :
- line : pour écrire une ligne ;
- action : pour afficher un bouton ;
- from : pour définir l'adresse de l'expéditeur ;
- subject : pour définir le sujet ;
- cc et bcc : pour faire des copies ;
- attach : pour joindre un document ;
- priority : pour fixer la priorité…
Vous constatez qu'on a une mise en forme de l'e-mail plutôt esthétique. Cette mise en forme est issue d'une vue par défaut dans le framework :
Si vous voulez modifier cette mise en forme, il faut publier cette vue :
php artisan vendor:publish --tag
=
laravel-notifications
Vous retrouvez alors la vue dans le dossier resources/views/vendor/notifications.
Vous pouvez vous demander maintenant comment est effectivement commandée cette notification. Ça se passe dans le trait CanResetPassword avec la méthode notify :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<?php
namespace
Illuminate\Auth\Passwords;
use
Illuminate\Auth\Notifications\ResetPassword as
ResetPasswordNotification;
trait
CanResetPassword
{
...
public
function
sendPasswordResetNotification($token
)
{
$this
->
notify(new
ResetPasswordNotification($token
));
}
}
Dans l'application d'exemple, on n'utilise pas directement la classe de notification prévue dans le framework parce qu'on veut localiser le texte :
2.
3.
4.
5.
6.
7.
8.
public function toMail()
{
return (new MailMessage)
->
line(__('
You are receiving this email because we received a password reset request for your account.
'
))
->
line(__('
Click the button below to reset your password:
'
))
->
action(__('
Reset Password
'
),
url('
password/reset
'
,
$this
->
token))
->
line(__('
If you did not request a password reset, no further action is required.
'
));
}
On a donc cette classe dans l'application :
et pour informer Laravel d'avoir à utiliser cette classe, on surcharge la méthode sendPasswordResetNotification qu'on a vue ci-dessus, présente dans le trait CanResetPassword au niveau de notre modèle User :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
<?php
...
use
App\Notifications\ResetPassword as
ResetPasswordNotification;
class
User extends
Authenticatable
{
use
Notifiable,
IngoingTrait;
...
public
function
sendPasswordResetNotification($token
)
{
$this
->
notify(new
ResetPasswordNotification($token
));
}
...
}
III-C. 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.
III-C-1. 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 la table « notifications » :
C'est dans cette table que sont stockées les notifications.
III-C-2. La notification▲
Comme pour les e-mails, il nous faut une classe de notification :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
<?php
namespace
App\Notifications;
use
Illuminate\Notifications\Notification;
use
App\Models\Post;
class
Commented extends
Notification
{
/**
* Post property.
*
*
@var
\App\Models\Post
*/
protected
$post
;
/**
* User id property.
*
*
@var
integer
*/
protected
$user_id
;
/**
* Create a new notification instance.
*
*
@param
Post
$post
*
@param
integer
$user_id
*/
public
function
__construct
(Post $post
,
$user_id
)
{
$this
->
post =
$post
;
$this
->
user_id =
$user_id
;
}
/**
* 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,
'user_id'
=>
$this
->
user_id,
];
}
}
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
) ainsi que l'identifiant de l'auteur (user_id). 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, le « slug » (pour générer l'URL) de l'article, ainsi que l'identifiant de l'auteur.
III-C-3. 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\Front\CommentController) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
public function store(CommentRequest $request
,
Post $post
,
$comment_id
=
null)
{
Comment::
create ([
'
body
'
=>
$request
->
input('
message
'
.
$comment_id
),
'
post_id
'
=>
$post
->
id,
'
user_id
'
=>
$request
->
user()->
id,
'
parent_id
'
=>
$comment_id
,
]
);
$post
->
user->
notify(new Commented($post
,
$request
->
user()->
id));
if (!
$request
->
user()->
valid) {
$request
->
session()->
flash('
warning
'
,
__('
Thanks for your comment. It will appear when an administrator has validated it.<br>Once you are validated your other comments immediately appear.
'
));
}
if($request
->
ajax()) {
return response()->
json();
}
return back();
}
La ligne qui crée la notification est celle-ci :
exactement comme on l'a vu pour les e-mails 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, slug et user_id).
III-C-4. Les routes et le contrôleur▲
On a deux routes :
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\Back\NotificationController) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
<?php
namespace
App\Http\Controllers\Back;
use
App\ {
Models\User,
Http\Controllers\Controller
};
use
Illuminate\ {
Http\Request,
Notifications\DatabaseNotification
};
class
NotificationController extends
Controller
{
/**
* Display a listing of the resource.
*
*
@param
\App\Models\User
$user
*
@return
\Illuminate\Http\Response
*/
public
function
index(User $user
)
{
return
view('back.notifications.index'
,
compact('user'
));
}
/**
* Update the specified resource in storage.
*
*
@param
\Illuminate\Http\Request
$request
*
@param
\Illuminate\Notifications\DatabaseNotification
$notification
*
@return
\Illuminate\Http\Response
*
@internal
param
int
$id
*/
public
function
update(Request $request
,
DatabaseNotification $notification
)
{
$notification
->
markAsRead();
if
($request
->
user()->
unreadNotifications->
isEmpty()) {
return
redirect()->
route('posts.index'
);
}
return
back();
}
}
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.
III-C-5. La vue index▲
Voici la vue pour afficher les notifications (resources/views/back/notifications/index.blade.php) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
@extends
('back.layout'
)
@section
('main'
)
<
div class
=
"
row
"
>
<
div class
=
"
col-md-12
"
>
<
div class
=
"
box
"
>
<
div class
=
"
box-body table-responsive
"
>
<
table class
=
"
table table-striped table-bordered
"
>
<
thead>
<
tr>
<
th>
@lang(
'Post'
)
<
/th
>
<
th>
@lang(
'Author'
)
<
/th
>
<
th>
@lang(
'Date'
)
<
/th
>
<
th>
@lang(
'Valid'
)
<
/th
>
<
th></th
>
<
/tr
>
<
/thead
>
<
tfoot>
<
tr>
<
th>
@lang(
'Post'
)
<
/th
>
<
th>
@lang(
'Author'
)
<
/th
>
<
th>
@lang(
'Date'
)
<
/th
>
<
th>
@lang(
'Valid'
)
<
/th
>
<
th></th
>
<
/tr
>
<
/tfoot
>
<
tbody>
@foreach
($user
->
unreadNotifications as
$notification
)
<
tr>
@php
$user
=
user($notification
->
data[
'user_id'
]
) @endphp
<
td><a href
=
"
{{
route('posts.display'
,
[
$notification
->
data[
'slug'
]]
) }}
"
>
{{
$notification
->
data[
'title'
]
}}
<
/a
></td
>
<
td>
{{
$user
->
name }}
<
/td
>
<
td>
{{
$notification
->
created_at->
formatLocalized('%c'
) }}
<
/td
>
<
td><input type
=
"
checkbox
"
name
=
"
valid
"
{{
$user
->
valid ?
'checked'
:
''
}}
disabled></td
>
<
td>
<
form action
=
"
{{
route('notifications.update'
,
[
$notification
->
id]
) }}
"
method
=
"
POST
"
>
{{
csrf_field() }}
{{
method_field('PUT'
) }}
<
input type
=
"
submit
"
class
=
"
btn btn-success btn-xs btn-block
"
value
=
"
@lang('Mark as read')
"
>
<
/form
>
<
/td
>
<
/tr
>
@endforeach
<
/tbody
>
<
/table
>
<
/div
>
<
/div
>
<!-- /.box -->
<
/div
>
<!-- /.col -->
<
/div
>
<!-- /.row -->
@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 « Marquer comme lu », 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). 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.
III-D. En résumé▲
- Laravel comporte un système complet et performant de notifications.
- On peut envoyer des e-mails avec les notifications, leur mise en forme est facilitée par la présence de puissantes méthodes et d'un template qu'on peut personnaliser.
- On peut stocker les notifications en base de données pour une utilisation ultérieure.