Эта статья была рецензирована Рафи Юнесом и Верном Анчетой . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
Современный веб-пользователь ожидает быть информированным обо всем, что происходит в приложении. Вы не хотите быть тем веб-сайтом, на котором даже нет раскрывающегося списка уведомлений, который можно найти не только на всех сайтах социальных сетей, но и везде в наши дни.
К счастью, с Laravel и Pusher реализация этой функциональности очень проста. Код, который мы напишем в этом уроке, можно найти здесь .
Изображение через Pusher.com
Уведомления в реальном времени
Для того, чтобы предоставить пользователям хороший опыт, уведомления должны отображаться в режиме реального времени. Одним из подходов является регулярная отправка AJAX-запроса на сервер и получение новейших уведомлений, если они существуют.
Лучшим подходом является использование возможностей WebSockets и получение уведомлений в момент их отправки. Это то, что мы собираемся использовать в этом уроке.
толкатель
Pusher — это веб-сервис для
… Интеграция двунаправленной функциональности в реальном времени с помощью WebSockets в веб и мобильные приложения.
У него очень простой API, но мы собираемся сделать его использование еще проще с Laravel Broadcasting и Laravel Echo .
В этом уроке мы собираемся добавить уведомления в реальном времени в существующий блог.
Основные функциональные возможности аналогичны уведомлениям Laravel в реальном времени с помощью Stream .
Мы начнем с этого репо, сделанного Кристофером Вунди (я немного его изменил), который представляет собой простой блог, где пользователи могут выполнять CRUD для постов.
Проэкт
инициализация
Сначала мы клонируем простой блог Laravel:
git clone https://github.com/vickris/simple-blog
Затем мы создадим базу данных MySQL и настроим переменные среды, чтобы предоставить приложению доступ к базе данных.
Давайте скопируем env.example
в .env
и обновим переменные, связанные с базой данных.
cp .env.example .env
DB_HOST=localhost DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret
.env
Теперь давайте установим зависимости проекта с
composer install
И запустите команду миграции и заполнения, чтобы заполнить базу данных некоторыми данными:
php artisan migrate --seed
Если вы запустите приложение и посетите /posts
вы сможете увидеть список сгенерированных сообщений.
Проверьте приложение, зарегистрируйте пользователя и создайте несколько сообщений. Это очень простое приложение, но оно отлично подходит для нашей демонстрации.
Следите за отношениями с пользователями
Мы хотим дать пользователям возможность следить за другими пользователями, а за ними следуют пользователи, поэтому мы должны создать отношения « Many To Many
между пользователями, чтобы это произошло.
Давайте создадим сводную таблицу, которая связывает пользователей с пользователями. Сделайте новую миграцию followers
:
php artisan make:migration create_followers_table --create=followers
Нам нужно добавить некоторые поля к этой миграции: user_id
для представления пользователя, который follows_id
, и поле follows_id
для представления пользователя, который подписан.
Обновите миграцию следующим образом:
public function up() { Schema::create('followers', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->index(); $table->integer('follows_id')->index(); $table->timestamps(); }); }
Теперь перейдем к созданию таблицы:
php artisan migrate
Если вы ознакомились со статьей о подходе к Stream, вы обнаружите, что до этого момента все почти одинаково. В следующей части мы собираемся достичь той же функциональности следования с другим подходом.
Давайте добавим методы отношений в модель 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(); } }
app/User.php
Теперь, когда пользовательская модель имеет необходимые отношения, followers
возвращают всех последователей пользователя, а затем возвращают всех, за кем follows
пользователь.
Нам понадобятся некоторые вспомогательные функции, позволяющие пользователю follow
другим пользователем и проверять, выполняет ли пользователь определенного пользователя.
// ... 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']); } }
app/User.php
Отлично. С установленной моделью пришло время составить список пользователей.
Список пользователей
Начнем с настройки необходимых маршрутов
//... 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'); });
routes/web.php
Затем пришло время создать новый контроллер для пользователей:
php artisan make:controller UsersController
Мы добавим к нему метод 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')); } }
app/Http/Controllers/UsersController.php
Метод нуждается в представлении. Давайте создадим представление users.index
и поместим в него эту разметку:
@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
resources/views/users/index.blade.php
Теперь вы можете посетить страницу /users
чтобы увидеть список пользователей.
Чтобы подписаться или отписаться
В UsersController
отсутствуют методы follow
и UsersController
. Давайте сделаем это, чтобы завершить эту часть.
//... 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); // sending a notification $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}"); } }
app/Http/Controllers/UsersController.php
Мы закончили со следующей функциональностью. Теперь мы можем следить за пользователями и отписываться от них на странице /users
.
Уведомления
Laravel предоставляет API для отправки уведомлений по нескольким каналам. Электронная почта, SMS, веб-уведомления и любые другие типы уведомлений могут быть отправлены с использованием класса уведомлений .
У нас будет два типа уведомлений:
- Следить за уведомлением: отправляется пользователю, когда за ним следует другой пользователь
- Уведомление о создании сообщения: отправляется подписчикам данного пользователя при создании нового сообщения.
Уведомление пользователя
Используя команды ремесленников, мы можем сгенерировать миграцию для уведомлений:
php artisan notifications:table
Давайте перенесем и создадим эту новую таблицу.
php artisan migrate
Мы начинаем с последующих уведомлений. Давайте выполним эту команду для создания класса уведомлений:
php artisan make:notification UserFollowed
Затем мы обновим файл класса уведомлений, который мы только что создали:
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, ]; } }
app/Notifications/UserFollowed.php
С помощью этих нескольких строк кода мы можем многого достичь. Во-первых, мы требуем, чтобы экземпляр $follower
вводился при создании этого уведомления.
Используя метод via
, мы сообщаем Laravel отправить это уведомление через канал database
. Когда Laravel сталкивается с этим, он создает новую запись в таблице уведомлений.
user_id
и type
уведомления устанавливаются автоматически, плюс мы можем расширить
уведомление с большим количеством данных. Вот для чего предназначена toDatabase
. Возвращенный массив будет добавлен в поле данных уведомления.
И, наконец, реализуя ShouldQueue
, Laravel автоматически поместит это уведомление в очередь, которая будет выполняться в фоновом режиме, что ускорит ответ. Это имеет смысл, потому что мы будем добавлять HTTP-вызовы, когда будем использовать Pusher позже.
Давайте инициируем уведомление, когда за пользователем последуют.
// ... 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); // add this to send a notification $user->notify(new UserFollowed($follower)); return back()->withSuccess("You are now friends with {$user->name}"); } return back()->withSuccess("You are already following {$user->name}"); } //... }
app/Http/Controllers/UsersController.php
Мы могли бы вызвать метод notify для модели User
потому что она уже использует черту Notifiable .
Любая модель, которую вы хотите уведомить, должна использовать ее для получения доступа к методу notify
.
Отметить уведомление как прочитанное
Уведомления будут содержать некоторую информацию и ссылку на ресурс. Например: когда пользователь получает уведомление о новом сообщении, уведомление должно содержать информативный текст, перенаправлять пользователя на сообщение при нажатии и помечаться как прочитанное.
Мы собираемся создать промежуточное программное обеспечение, которое проверяет, есть ли в запросе ?read=notification_id
input, и помечает его как прочитанное.
Давайте сделаем промежуточное программное обеспечение с помощью следующей команды:
php artisan make:middleware MarkNotificationAsRead
Затем давайте поместим этот код в метод handle
промежуточного программного обеспечения:
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); } }
app/Http/Middleware/MarkNotificationAsRead.php
Для того, чтобы наше промежуточное программное обеспечение выполнялось для каждого запроса, мы добавим его в $middlewareGroups
.
//... class Kernel extends HttpKernel { //... protected $middlewareGroups = [ 'web' => [ //... \App\Http\Middleware\MarkNotificationAsRead::class, ], // ... ]; //... }
app/Http/Kernel.php
После этого давайте покажем несколько уведомлений.
Отображение уведомлений
Мы должны показать список уведомлений, используя AJAX, а затем обновить его в режиме реального времени с помощью Pusher. Во-первых, давайте добавим метод notifications
в контроллер:
// ... class UsersController extends Controller { // ... public function notifications() { return auth()->user()->unreadNotifications()->limit(5)->get()->toArray(); } }
app/Http/Controllers/UsersController.php
Это вернет последние 5 непрочитанных уведомлений. Нам просто нужно добавить маршрут, чтобы сделать его доступным.
//... Route::group([ 'middleware' => 'auth' ], function () { // ... Route::get('/notifications', 'UsersController@notifications'); });
routes/web.php
Теперь добавьте выпадающий список для уведомлений в шапке.
<head> <!-- // ... // --> <!-- Scripts --> <script> window.Laravel = <?php echo json_encode([ 'csrfToken' => csrf_token(), ]); ?> </script> <!-- This makes the current user's id available in 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> <!-- // ... // -->
resources/views/layouts/app.blade.php
Мы также добавили глобальную переменную window.Laravel.userId
в скрипт, чтобы получить идентификатор текущего пользователя.
JavaScript и SASS
Мы собираемся использовать Laravel Mix для компиляции JavaScript и SASS. Во-первых, нам нужно установить пакеты npm.
npm install
Теперь давайте добавим этот код в app.js
:
window._ = require('lodash'); window.$ = window.jQuery = require('jquery'); require('bootstrap-sass'); var notifications = []; const NOTIFICATION_TYPES = { follow: 'App\\Notifications\\UserFollowed' };
app/resources/assets/js/app.js
Это всего лишь инициализация. Мы собираемся использовать notifications
для хранения всех объектов уведомлений, независимо от того, извлекаются ли они через AJAX или Pusher.
Вы, наверное, догадались, NOTIFICATION_TYPES
содержит типы уведомлений.
Далее давайте «ПОЛУЧИМ» уведомления через AJAX.
//... $(document).ready(function() { // check if there's a logged in user if(Laravel.userId) { $.get('/notifications', function (data) { addNotifications(data, "#notifications"); }); } }); function addNotifications(newNotifications, target) { notifications = _.concat(notifications, newNotifications); // show only last 5 notifications notifications.slice(0, 5); showNotifications(notifications, target); }
app/resources/assets/js/app.js
Благодаря этому мы получаем последние уведомления от нашего API и помещаем их в раскрывающийся список.
Внутри addNotifications
мы объединяем текущие уведомления с новыми, используя Lodash , и принимаем только последние 5, которые будут показаны.
Нам нужно еще несколько функций, чтобы закончить работу.
//... 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'); } }
app/resources/assets/js/app.js
Эта функция создает строку всех уведомлений и помещает ее в раскрывающийся список.
Если не было получено ни одного уведомления, отображается просто «Нет уведомлений».
Он также добавляет класс к выпадающей кнопке, которая просто изменит свой цвет при наличии уведомлений. Это немного похоже на уведомления Github.
Наконец, некоторые вспомогательные функции для создания строк уведомлений.
//... // Make a single notification string function makeNotification(notification) { var to = routeNotification(notification); var notificationText = makeNotificationText(notification); return '<li><a href="' + to + '">' + notificationText + '</a></li>'; } // get the notification route based on it's type function routeNotification(notification) { var to = '?read=' + notification.id; if(notification.type === NOTIFICATION_TYPES.follow) { to = 'users' + to; } return '/' + to; } // get the notification text based on it's type 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; }
app/resources/assets/js/app.js
Теперь мы просто добавим это в наш файл app.scss
:
//... #notifications.has-notifications { color: #bf5329 }
app/resources/assets/sass/app.scss
Давайте скомпилируем активы:
npm run dev
Если вы попытаетесь подписаться на пользователя сейчас, он получит уведомление. Когда они нажмут на нее, они будут перенаправлены на /users
, плюс уведомление исчезнет.
Новое почтовое уведомление
Мы собираемся уведомить подписчиков, когда пользователь создает новый пост.
Начнем с создания класса уведомлений.
php artisan make:notification NewPost
Давайте обновим сгенерированный класс следующим образом:
// .. 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, ]; } }
app/Notifications/NewArticle.php
Далее нам нужно отправить уведомление. Есть несколько способов сделать это.
Мне нравится использовать Eloquent Observers .
Давайте сделаем обозреватель для Post
и послушаем его события. Мы создадим новый класс: 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)); } } }
Затем зарегистрируйте наблюдателя в AppServiceProvider
:
//... use App\Observers\PostObserver; use App\Post; class AppServiceProvider extends ServiceProvider { //... public function boot() { Post::observe(PostObserver::class); } //... }
app/Providers/AppServiceProvider.php
Теперь нам просто нужно отформатировать сообщение для отображения в 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; }
app/resources/assets/js/app.js
И вуаля! Пользователи получают уведомления о подписках и новых сообщениях! Идите и попробуйте!
В режиме реального времени с Pusher
Пришло время использовать Pusher для получения уведомлений в режиме реального времени через веб-сокеты.
Зарегистрируйте бесплатную учетную запись Pusher на pusher.com и создайте новое приложение.
... BROADCAST_DRIVER=pusher PUSHER_KEY= PUSHER_SECRET= PUSHER_APP_ID=
Установите параметры своей учетной записи в файле конфигурации broadcasting
:
//... 'connections' => [ 'pusher' => [ //... 'options' => [ 'cluster' => 'eu', 'encrypted' => true ], ], //...
config/broadcasting.php
Затем мы зарегистрируем App\Providers\BroadcastServiceProvider
в массиве provider.
// ... 'providers' => [ // ... App\Providers\BroadcastServiceProvider //... ], //...
config/app.php
Мы должны установить PHP Pusher SDK и Laravel Echo сейчас:
composer require pusher/pusher-php-server
npm install --save laravel-echo pusher-js
Мы должны установить данные уведомления для трансляции. Давайте обновим уведомление 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, ], ]; } }
app/Notifications/UserFollowed.php
И 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, ], ]; } }
app/Notifications/NewPost.php
Последнее, что нам нужно сделать, это обновить наш JS. Откройте app.js
и добавьте следующий код
// ... 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'); }); } });
app/resources/assets/js/app.js
И мы сделали здесь. Уведомления добавляются в режиме реального времени. Теперь вы можете поиграть с приложением и посмотреть, как обновляются уведомления.
Вывод
У Pusher очень простой API, который делает получение событий в реальном времени невероятно простым. В сочетании с уведомлениями Laravel мы можем отправлять уведомления по нескольким каналам (электронная почта, SMS, Slack и т. Д.) Из одного места. В этом уроке мы добавили функциональные возможности для отслеживания пользователей в простой блог и улучшили его с помощью вышеупомянутых инструментов, чтобы получить плавную функциональность в реальном времени.
У Pusher и Laravel есть гораздо больше уведомлений: в тандеме сервисы позволяют отправлять сообщения паба / подпрограммы в реальном времени на браузеры, мобильные телефоны и устройства IOT. Также есть API присутствия для получения статуса онлайн / оффлайн пользователей.
Пожалуйста, проверьте их соответствующую документацию ( Pusher docs , Pusher tutorials , Laravel docs ), чтобы изучить их более подробно и использовать их истинный потенциал.
Позвольте мне услышать, что вы создали с этими технологиями в комментариях.