Cómo agregar notificaciones en tiempo real a Laravel usando Pusher

Se preparó una traducción del artículo especialmente para los estudiantes del curso "Marco Laravel" .




Un usuario web moderno espera ser informado sobre todo lo que sucede en la aplicación. No querrá ser ese sitio web que ni siquiera tiene una lista desplegable de notificaciones, que ahora se puede encontrar no solo en todos los sitios de redes sociales, sino en general en todas partes en estos días.

Afortunadamente, con Laravel y Pusher, la implementación de esta funcionalidad es bastante simple.

Notificaciones en tiempo real


Para proporcionar una experiencia de usuario positiva, las notificaciones deben mostrarse en tiempo real. Un enfoque es enviar regularmente una solicitud AJAX al servidor y recibir las últimas notificaciones, si las hay.

El mejor enfoque es utilizar las capacidades de WebSockets y recibir notificaciones cuando se envían. Esto es exactamente lo que vamos a implementar en este artículo.

Arribista


Pusher es un servicio web para integrar la funcionalidad bidireccional en tiempo real a través de WebSockets en aplicaciones web y móviles.

Tiene una API muy simple, pero haremos que su uso sea aún más fácil con Laravel Broadcasting y Laravel Echo.

En este artículo, agregaremos notificaciones en tiempo real a un blog existente.

Proyecto


Inicialización


Primero, clonamos un simple blog de Laravel:

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

Luego crearemos la base de datos MySQL y configuraremos las variables de entorno para dar acceso a la aplicación a la base de datos.

Vamos copia env.exampleen .envy actualizar las variables asociadas con la base de datos.

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

Ahora instalemos las dependencias del proyecto usando

composer install

Y ejecute el comando de migración y poblado para llenar la base de datos con algunos datos:

php artisan migrate --seed

Si ejecuta la aplicación y va a /posts, puede ver una lista de publicaciones generadas.

Verifique la aplicación, registre al usuario y cree algunos mensajes. Esta es una aplicación muy simple, pero es excelente para la demostración.

Suscríbase a los usuarios


Nos gustaría darles a los usuarios la oportunidad de suscribirse entre sí, por lo que debemos crear una relación de muchos a muchos entre los usuarios para lograrlo.

Creemos una tabla dinámica que vincule usuarios con usuarios. Hagamos una nueva migración followers:

php artisan make:migration create_followers_table --create=followers

Necesitamos agregar varios campos a esta migración: user_idpara representar al usuario que está suscrito, y un campo follows_idpara representar al usuario al que está suscrito.

Actualice la migración de la siguiente manera:

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

Ahora pasemos a crear la tabla:

php artisan migrate

Agreguemos métodos de relación al modelo 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();
    }
}

Ahora que el modelo Usertiene las relaciones necesarias, followersdevuelve todos los suscriptores del usuario y followsdevuelve todos aquellos a los que está suscrito.

Necesitaremos algunas funciones auxiliares que permitan al usuario suscribirse a otros usuarios follow, y verificar si el usuario está suscrito a un usuario específico 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']);
    }

}

Multa. Después de preparar el modelo, debe hacer una lista de usuarios.

una lista de usuarios


Comencemos por identificar las rutas necesarias.

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

Entonces es hora de crear un nuevo controlador para los usuarios:

php artisan make:controller UsersController

Le agregaremos un método 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'));
    }
}

El método necesita ser introducido. Creemos una vista users.indexy agreguemos el siguiente marcado:

@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

Ahora puede visitar la página /userspara ver la lista de usuarios.

Seguir y dejar de seguir


En UsersControllermétodos faltantes followy unfollow. Vamos a implementarlos para completar esta parte.

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

Hemos terminado con esta funcionalidad. Ahora podemos suscribirnos a los usuarios y cancelar su suscripción en la página /users.

Notificaciones


Laravel proporciona una API para enviar notificaciones a través de múltiples canales. El correo electrónico, SMS, notificaciones web y cualquier otro tipo de notificaciones se pueden enviar utilizando la clase de Notificación .

Tendremos dos tipos de notificaciones:

  • Notificación de suscripción: se envía al usuario cuando otro usuario se suscribe
  • Notificación de publicación: se envía a los suscriptores de este usuario cuando se publica una nueva publicación.

Notificación de suscripción


Usando comandos artesanales, podemos generar una migración para notificaciones:

php artisan notifications:table

Hagamos la migración y creemos esta tabla.

php artisan migrate

Comenzaremos con notificaciones de suscripción. Ejecutemos este comando para crear una clase de notificación:

php artisan make:notification UserFollowed

Luego modificamos el archivo de clase de notificación que acabamos de crear:

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

Con estas pocas líneas de código, ya podemos lograr mucho. Primero, requerimos que la instancia $followerse implemente cuando se genera esta notificación.

Usando el método via, le decimos a Laravel que envíe esta notificación a través del canal database. Cuando Laravel se encuentra con esto, crea una nueva entrada en la tabla de notificaciones.

user_idy las typenotificaciones se configuran automáticamente, además podemos expandir la notificación con datos adicionales. Para eso es eso toDatabase. La matriz devuelta se agregará al campo de datanotificación.

Y finalmente, gracias a la implementación.ShouldQueue, Laravel colocará automáticamente esta notificación en una cola que se ejecutará en segundo plano, lo que acelerará la respuesta. Esto tiene sentido porque agregaremos llamadas HTTP cuando usemos Pusher más tarde.

Implementemos una notificación de suscripción de usuario.

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

    //...
}

Podemos llamar al método de notificación para el modelo de Usuario porque ya usa el rasgo Notificable.

Cualquier modelo que desee notificar debe usarlo para obtener acceso al método de notificación.

Marcamos la notificación como leída Las

notificaciones contendrán cierta información y un enlace al recurso. Por ejemplo: cuando el usuario recibe una notificación sobre un nuevo mensaje, la notificación debe contener texto informativo, redirigir al usuario al mensaje cuando se presiona y marcarse como leído.

Vamos a crear una capa que verificará si hay una ocurrencia en la solicitud ?read=notification_idy la marcará como leída.

Hagamos esta capa con el siguiente comando:

php artisan make:middleware MarkNotificationAsRead

Entonces pongamos este código en el método de handlecapa intermedia:

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

Para que nuestra capa se ejecute para cada solicitud, la agregaremos a $middlewareGroups.

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

Después de eso, vamos a mostrar las notificaciones.

Mostrar notificaciones


Necesitamos mostrar la lista de notificaciones usando AJAX, y luego actualizarla en tiempo real usando Pusher. Primero, agreguemos un método notificationsal controlador:

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

Este código devolverá las últimas 5 notificaciones no leídas. Solo necesitamos agregar una ruta para que sea accesible.

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

Ahora agregue una lista desplegable para notificaciones en el encabezado.

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

También agregamos una variable global window.Laravel.userIdal script para obtener la ID de usuario actual.

JavaScript y SASS


Vamos a usar Laravel Mix para compilar JavaScript y SASS. Primero, necesitamos instalar paquetes npm.

npm install

Ahora agreguemos este código a app.js:

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

Esto es solo inicialización. Vamos a utilizar notificaciones para almacenar todos los objetos de notificación, ya sea que se recuperen a través de AJAX o Pusher.

Como probablemente ya haya adivinado, NOTIFICATION_TYPEScontiene tipos de notificación.

Ahora obtengamos notificaciones ("GET") a través de 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);
}

Gracias a este código, recibimos las últimas notificaciones de nuestra API y las colocamos en una lista desplegable.

En el interior, addNotificationscombinamos notificaciones existentes con otras nuevas usando Lodash , y tomamos solo las últimas 5, que se mostrarán.

Necesitamos algunas funciones más para terminar el trabajo.

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

Esta función crea una línea de todas las notificaciones y la coloca en la lista desplegable.
Si no se han recibido notificaciones, simplemente se muestra "Sin notificaciones".

También agrega una clase al botón desplegable, que simplemente cambia de color cuando hay notificaciones. Un poco como las notificaciones de Github.

Finalmente, algunas funciones auxiliares para crear cadenas de notificación.

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

Ahora solo agregamos esto a nuestro archivo app.scss:

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

Vamos a compilar los activos:

npm run dev

Ahora, si intenta suscribirse a un usuario, recibirá una notificación. Cuando hace clic en él, será redirigido /usersy la notificación desaparecerá.

Nueva notificación posterior


Notificaremos a los suscriptores cuando un usuario publique una nueva publicación.

Comencemos creando una clase de notificación.

php artisan make:notification NewPost

Modifiquemos la clase generada de la siguiente manera:

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

A continuación, debemos enviar una notificación. Hay varias formas de hacerlo.

Me gusta usar observadores elocuentes.

Creemos un observador para el Post y escuchemos sus eventos. Crearemos una nueva clase: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));
        }
    }
}

Luego registre al observador en AppServiceProvider:

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

Ahora solo necesitamos formatear el mensaje para mostrarlo 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;
}

¡Y voilá! ¡Los usuarios reciben notificaciones de suscripciones y nuevas publicaciones! ¡Inténtalo tú mismo!

Salga en tiempo real con Pusher


Es hora de usar Pusher para recibir notificaciones en tiempo real a través de sockets web.

Regístrese para obtener una cuenta Pusher gratuita en pusher.com y cree una nueva aplicación.

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

Establezca los parámetros para su cuenta en el archivo de configuración broadcasting:

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

Luego nos registraremos App\Providers\BroadcastServiceProvideren la matriz providers.

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

Ahora necesitamos instalar PHP SDK y Laravel Echo de Pusher:

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

Necesitamos configurar datos de notificación para la transmisión. Modifiquemos la notificación 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,
            ],
        ];
    }
}

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

Lo último que debemos hacer es actualizar nuestro JS. Abre app.jsy agrega el siguiente código

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

Y eso es todo. Las notificaciones se agregan en tiempo real. Ahora puedes jugar con la aplicación y ver cómo se actualizan las notificaciones.

Conclusión


Pusher tiene una API muy fácil de usar que hace que la implementación de eventos en tiempo real sea increíblemente simple. En combinación con las notificaciones de Laravel, podemos enviar notificaciones a través de varios canales (correo electrónico, SMS, Slack, etc.) desde un solo lugar. En esta guía, agregamos funcionalidad para rastrear la actividad del usuario en un blog simple y lo mejoramos usando las herramientas anteriores para obtener una funcionalidad fluida en tiempo real.

Pusher y Laravel tienen muchas más notificaciones: en conjunto, los servicios le permiten enviar mensajes de pub / sub en tiempo real a navegadores, teléfonos móviles y dispositivos IOT. También hay una API para obtener el estado del usuario en línea / fuera de línea.

Consulte su documentación (documentos de Pusher, Tutoriales de Pusher , documentos de Laravel ) para aprender más sobre su uso y verdadero potencial.

Si tiene comentarios, preguntas o recomendaciones, ¡siéntase libre de compartirlos en los comentarios a continuación!



Aprende más sobre el curso.



All Articles