A translation of the article was prepared especially for students of the “Framework Laravel” course .
A modern web user expects to be informed about everything that happens in the application. You would not want to be the website that does not even have a drop-down list of notifications, which can now be found not only on all social networking sites, but generally everywhere these days.Fortunately, with Laravel and Pusher, the implementation of this functionality is quite simple.Real time notifications
In order to provide a positive user experience, notifications should be displayed in real time. One approach is to regularly send an AJAX request to the server and receive the latest notifications, if any.The best approach is to use the capabilities of WebSockets and receive notifications when they are sent. This is exactly what we are going to implement in this article.Pusher
Pusher is a web service for integrating real-time bi-directional functionality through WebSockets into web and mobile applications.It has a very simple API, but we're going to make using it even easier with Laravel Broadcasting and Laravel Echo.In this article, we are going to add real-time notifications to an existing blog.Project
Initialization
First, we clone a simple Laravel blog:git clone https:
Then we will create the MySQL database and set up the environment variables to give the application access to the database.Let's copy env.example
in .env
and update the variables associated with the database.cp .env.example .envDB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
Now let's install the project dependencies usingcomposer install
And run the migration and populate command to populate the database with some data:php artisan migrate --seed
If you run the application and go to /posts
, you can see a list of generated posts.Check the application, register the user and create some messages. This is a very simple application, but it is great for demonstration.Subscribe to Users
We would like to give users the opportunity to subscribe to each other, so we must create a many-to-many relationship between users in order to realize this.Let's create a pivot table that links users to users. Let's make a new migration followers
:php artisan make:migration create_followers_table --create=followers
We need to add several fields to this migration: user_id
to represent the user who is subscribed, and a field follows_id
to represent the user that they are subscribed to.Update the migration as follows:public function up()
{
Schema::create('followers', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->index();
$table->integer('follows_id')->index();
$table->timestamps();
});
}
Now let's move on to creating the table:php artisan migrate
Let's add relationship methods to the model 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();
}
}
Now that the model User
has the necessary relationships, it followers
returns all the subscribers of the user, and follows
returns all those to whom the user is subscribed.We will need some auxiliary functions that allow the user to subscribe to other users - follow
, and to check whether the user is subscribed to a specific user - 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']);
}
}
Fine. After preparing the model, you need to make a list of users.a list of users
Let's start by identifying the necessary routes./...
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');
});
Then it's time to create a new controller for users:php artisan make:controller UsersController
We will add a method to it 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'));
}
}
The method needs to be introduced. Let's create a view users.index
and put the following markup in it:@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
Now you can visit the page /users
to see the list of users.Follow and Unfollow
In UsersController
missing methods follow
and unfollow
. Let's implement them to complete this part.
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}");
}
}
We are done with this functionality. Now we can subscribe to users and unsubscribe from them on the page /users
.Notifications
Laravel provides an API for sending notifications across multiple channels. Email, SMS, web notifications and any other types of notifications can be sent using the Notification class .We will have two types of notifications:- Subscription notification: sent to the user when another user subscribes to it
- Post notification: sent to subscribers of this user when a new post is published.
Subscription Notification
Using artisan commands, we can generate a migration for notifications:php artisan notifications:table
Let's do the migration and create this table.php artisan migrate
We will start with subscription notifications. Let's run this command to create a notification class:php artisan make:notification UserFollowed
Then we modify the notification class file that we just created: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,
];
}
}
With these few lines of code, we can already achieve a lot. First, we require that the instance $follower
be implemented when this notification is generated.Using the method via
, we tell Laravel to send this notification through the channel database
. When Laravel encounters this, it creates a new entry in the notification table.user_id
and type
notifications are set automatically, plus we can expand the notification with additional data. That's what it is for toDatabase
. The returned array will be added to the data
notification field .And finally, thanks to the implementationShouldQueue
, Laravel will automatically place this notification in a queue that will run in the background, which will speed up the response. This makes sense because we will add HTTP calls when we use Pusher later.Let's implement a user subscription notification.
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}");
}
}
We can call the notify method for the User model because it already uses the Notifiable trait.Any model you want to notify should use it to gain access to the notify method.We mark the notification as readNotifications will contain some information and a link to the resource. For example: when the user receives a notification about a new message, the notification should contain informative text, redirect the user to the message when pressed and be marked as read.We are going to create a layer that will check if there is an occurrence in the request ?read=notification_id
and marks it as read.Let's make this layer with the following command:php artisan make:middleware MarkNotificationAsRead
Then let's put this code in the handle
interlayer method :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);
}
}
In order for our layer to be executed for each request, we will add it to $middlewareGroups
.
class Kernel extends HttpKernel
{
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\MarkNotificationAsRead::class,
],
];
}
After that, let's get the notifications displayed.Display notifications
We need to show the notification list using AJAX, and then update it in real time using Pusher. First, let's add a method notifications
to the controller:
class UsersController extends Controller
{
public function notifications()
{
return auth()->user()->unreadNotifications()->limit(5)->get()->toArray();
}
}
This code will return the last 5 unread notifications. We just need to add a route to make it accessible.
Route::group([ 'middleware' => 'auth' ], function () {
Route::get('/notifications', 'UsersController@notifications');
});
Now add a drop-down list for notifications in the header.<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
<!--
<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>
<!-- // ... // -->
We also added a global variable window.Laravel.userId
to the script to get the current user ID.JavaScript and SASS
We are going to use Laravel Mix to compile JavaScript and SASS. First, we need to install npm packages.npm install
Now let's add this code to app.js
:window._ = require('lodash');
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
var notifications = [];
const NOTIFICATION_TYPES = {
follow: 'App\\Notifications\\UserFollowed'
};
This is just initialization. We are going to use notifications to store all notification objects, whether they are retrieved through AJAX or Pusher.As you probably already guessed, it NOTIFICATION_TYPES
contains notification types.Now let's get (“GET”) notifications via AJAX.
$(document).ready(function() {
if(Laravel.userId) {
$.get('/notifications', function (data) {
addNotifications(data, "#notifications");
});
}
});
function addNotifications(newNotifications, target) {
notifications = _.concat(notifications, newNotifications);
notifications.slice(0, 5);
showNotifications(notifications, target);
}
Thanks to this code, we receive the latest notifications from our API and put them in a drop-down list.Inside, addNotifications
we combine existing notifications with new ones using Lodash , and take only the last 5, which will be shown.We need a few more functions to finish the job.
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');
}
}
This function creates a line of all notifications and places it in the drop-down list.If no notifications have been received, simply “No notifications” is displayed.It also adds a class to the drop-down button, which simply changes color when there are notifications. A bit like Github notifications.Finally, some helper functions for creating notification strings.
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;
}
Now we just add this to our file app.scss
:
color:
}
Let's compile the assets:npm run dev
Now if you try to subscribe to a user, he will receive a notification. When he clicks on it, he will be redirected to /users
, and the notification itself disappears.New Post Notification
We are going to notify subscribers when a user publishes a new post.Let's start by creating a notification class.php artisan make:notification NewPost
Let's modify the generated class as follows:
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,
];
}
}
Next we need to send a notification. There are several ways to do this.I like to use Eloquent watchers.Let's create an observer for the Post and listen to its events. We will create a new class: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));
}
}
}
Then register the observer in AppServiceProvider
:
use App\Observers\PostObserver;
use App\Post;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Post::observe(PostObserver::class);
}
}
Now we just need to format the message for display in 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;
}
And voila! Users are notified of subscriptions and new posts! Try it yourself!Exit in real time with Pusher
It's time to use Pusher to receive real-time notifications via web sockets.Register for a free Pusher account at pusher.com and create a new app....
BROADCAST_DRIVER=pusher
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=
Set the parameters for your account in the configuration file broadcasting
:
'connections' => [
'pusher' => [
'options' => [
'cluster' => 'eu',
'encrypted' => true
],
],
Then we will register App\Providers\BroadcastServiceProvider
in the array providers
.
'providers' => [
App\Providers\BroadcastServiceProvider
],
Now we need to install the PHP SDK and Laravel Echo from Pusher:composer require pusher/pusher-php-server
npm install --save laravel-echo pusher-js
We need to set up notification data for broadcast. Let's modify the 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,
],
];
}
}
And 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,
],
];
}
}
The last thing we need to do is update our JS. Open app.js
and add the following code
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');
});
}
});
And that is all. Notifications are added in real time. Now you can play with the application and see how notifications are updated.Conclusion
Pusher has a very user-friendly API that makes real-time event implementation incredibly simple. In combination with Laravel notifications, we can send notifications via several channels (email, SMS, Slack, etc.) from one place. In this guide, we added functionality to track user activity in a simple blog and improved it using the above tools to get some smooth real-time functionality.Pusher and Laravel have much more notifications: in a tandem, services allow you to send pub / sub messages in real time to browsers, mobile phones and IOT devices. There is also an API for obtaining online / offline user status.Please see their documentation ( Pusher docs, Pusher tutorials , Laravel docs ) to learn more about their use and true potential.If you have any comments, questions or recommendations, feel free to share them in the comments below!
Learn more about the course.