تنظیم اعلانات برای کاربران مشترک (Subscribed Users) در لاراول
در این آموزش از لیداوب، چگونگی تنظیم اعلانها یا نوتیفیکیشنها را برای کاربران مشترک (Subscribed Users) در لاراول را بررسی خواهیم کرد. برای یادگیری بیشتر این موضوع با ما همراه باشید.
به عنوان مثال، هر زمان که یک موضوع (thread) پاسخ جدیدی را دریافت میکند، میتوانیم به طور خودکار یک ایمیل ارسال کنیم یا یک کامپوننت نوتیفیکیشن را در خود وب سایت وارد کنیم.
تنظیم اعلانات برای کاربران مشترک (Subscribed Users) در لاراول
ما میخواهیم از ویژگیهای Notifiable
پیشفرض در لاراول که برای تنظیم انواع اعلانات استفاده میشود، بهره ببریم. میتوان از ابزار آرتیسان در لاراول، برای این کار استفاده کرد.
[email protected]:~/Code/forumio$ php artisan help notifications:table
Usage:
notifications:table
Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
--env[=ENV] The environment the command should run under
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Help:
Create a migration for the notifications table
بنابراین، ما این دستور را اجرا کرده و مایگریشنهای پایگاه داده را نیز ایجاد میکنیم:
[email protected]:~/Code/forumio$ php artisan notifications:table
Migration created successfully!
[email protected]:~/Code/forumio$ php artisan migrate
Migrating: 2018_02_21_171105_create_notifications_table
Migrated: 2018_02_21_171105_create_notifications_table
اکنون پایگاه داده شامل جدول جدید notifications
است که تمام فیلدهای لازم برای شروع کار با نوتیفیکیشنها را دارد. ما میتوانیم از این جدول برای اعلانهای مشترکین (Subscribed) استفاده کنیم.
افزودن کلاس Notification
تنظیم جدول پایگاه داده اولین قسمت از تنظیم اعلانها (notifications) در لاراول است، ایجاد کلاس notification مرحله بعدی است. همچنین یک دستور آرتیسان برای کمک به این امر وجود دارد که میتوانیم از آن نیز استفاده کنیم. یک کلاس اعلان ThreadWasUpdated
به صورت زیر ایجاد میکنیم.
[email protected]:~/Code/forumio$ php artisan make:notification ThreadWasUpdated
Notification created successfully.
این دستور یک دایرکتوری جدید نوتیفیکیشن و یک کلاس نوتیفیکیشن جدید را در پروژه ایجاد میکند.
محتوای فایل جدید را به صورت زیر بروزرسانی میکنیم.
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
class ThreadWasUpdated extends Notification
{
protected $thread;
protected $reply;
public function __construct($thread, $reply)
{
$this->thread = $thread;
$this->reply = $reply;
}
public function via()
{
return ['database'];
}
public function toArray()
{
return [
'message' => $this->reply->owner->name . ' replied to ' . $this->thread->title,
'link' => $this->reply->path()
];
}
}
ThreadSubscription
متعلق به یک کاربر است. بنابراین برای نمایش آن باید مدل ThreadSubscription
را نیز بروزرسانی کنیم. میتوانیم یک رابطه موضوع (thread relationship) و یک متد notify()
را نیز اضافه کنیم.
<?php
namespace App;
use App\Notifications\ThreadWasUpdated;
use Illuminate\Database\Eloquent\Model;
class ThreadSubscription extends Model
{
protected $guarded = [];
public function user()
{
return $this->belongsTo(User::class);
}
public function thread()
{
return $this->belongsTo(Thread::class);
}
public function notify($reply)
{
$this->user->notify(new ThreadWasUpdated($this->thread, $reply));
}
}
اعلانها به نقاط پایانی نیاز دارند
برای پشتیبانی از دریافت و حذف اعلانها، باید چند مسیر را به فایل web.php
اضافه کنیم.
<?php
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', '[email protected]')->name('home');
Route::get('/threads', '[email protected]');
Route::get('/threads/create', '[email protected]');
Route::get('/threads/{channel}/{thread}', '[email protected]');
Route::delete('/threads/{channel}/{thread}', '[email protected]');
Route::post('/threads', '[email protected]');
Route::get('/threads/{channel}', '[email protected]');
Route::get('/threads/{channel}/{thread}/replies', '[email protected]');
Route::post('/threads/{channel}/{thread}/replies', '[email protected]');
Route::patch('/replies/{reply}', '[email protected]');
Route::delete('/replies/{reply}', '[email protected]');
Route::post('/threads/{channel}/{thread}/subscriptions', '[email protected]')->middleware('auth');
Route::delete('/threads/{channel}/{thread}/subscriptions', '[email protected]')->middleware('auth');
Route::post('/replies/{reply}/favorites', '[email protected]');
Route::delete('/replies/{reply}/favorites', '[email protected]');
Route::get('/profiles/{user}', '[email protected]')->name('profile');
Route::get('/profiles/{user}/notifications', '[email protected]');
Route::delete('/profiles/{user}/notifications/{notification}', '[email protected]');
این بدان معناست که ما به یک کنترلر جدید به نام UserNotificationsController
با متدهای index()
و destroy()
نیاز داریم.
[email protected]:~/Code/forumio$ php artisan make:controller UserNotificationsController
Controller created successfully.
کد زیر را نیز اضافه میکنیم.
<?php
namespace App\Http\Controllers;
class UserNotificationsController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
return auth()->user()->unreadNotifications;
}
public function destroy($user, $notificationId)
{
auth()->user()->notifications()->findOrFail($notificationId)->markAsRead();
}
}
استفاده از رویدادها برای اعلانهای جدید
برای پیکربندی اعلانها، میتوانیم از رویدادهای لاراول نیز استفاده کنیم. اول از همه، ما EventServiceProvider.php
را به صورت زیر تغییر میدهیم.
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
'App\Events\ThreadReceivedNewReply' => [
'App\Listeners\NotifySubscribers'
],
];
public function boot()
{
parent::boot();
//
}
}
با متغیر $listen
وارد شده، اکنون میتوانیم دستور generate event
را ایجاد کنیم تا فایلهای مورد نیاز را به صورت خودکار بسازیم.
[email protected]:~/Code/forumio$ php artisan event:generate
Events and listeners generated successfully!
میتوان این دو دایرکتوری جدید را با فایلهای مرتبط مورد نیاز اضافه کرد.
میتوانیم کد را به صورت زیر برای هر دو ThreadReceivedNewReply.php
و NotifySubscribers.php
وارد کنیم.
<?php
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ThreadReceivedNewReply
{
use Dispatchable, SerializesModels;
public $reply;
public function __construct($reply)
{
$this->reply = $reply;
}
}
<?php
namespace App\Listeners;
use App\Events\ThreadReceivedNewReply;
class NotifySubscribers
{
public function handle(ThreadReceivedNewReply $event)
{
$event->reply->thread->subscriptions
->where('user_id', '!=', $event->reply->user_id)
->each
->notify($event->reply);
}
}
صدا زدن رویداد جدید
اکنون، هنگامی که پاسخ جدید اضافه میشود، نوتیفیکیشن نیز ایجاد میشود. در حال حاضر یک متد addReply()
در مدل Thread داریم که باید آن را بروزرسانی کنیم. میتوانیم از رویدادهای لاراول نیز استفاده کنیم.
<?php
namespace App;
use App\Events\ThreadReceivedNewReply;
use Illuminate\Database\Eloquent\Model;
use Tests\Feature\ActivityTest;
class Thread extends Model
{
use RecordsActivity;
protected $guarded = [];
protected $with = ['creator', 'channel'];
protected $appends = ['isSubscribedTo'];
protected static function boot()
{
parent::boot();
static::addGlobalScope('replyCount', function ($builder) {
$builder->withCount('replies');
});
static::deleting(function ($thread) {
$thread->replies->each->delete();
});
}
public function path()
{
return '/threads/' . $this->channel->slug . '/' . $this->id;
}
public function replies()
{
return $this->hasMany(Reply::class);
}
public function creator()
{
return $this->belongsTo(User::class, 'user_id');
}
public function channel()
{
return $this->belongsTo(Channel::class);
}
public function addReply($reply)
{
$reply = $this->replies()->create($reply);
event(new ThreadReceivedNewReply($reply));
return $reply;
}
public function scopeFilter($query, $filters)
{
return $filters->apply($query);
}
public function subscribe($userId = null)
{
$this->subscriptions()->create([
'user_id' => $userId ?: auth()->id()
]);
return $this;
}
public function unsubscribe($userId = null)
{
$this->subscriptions()
->where('user_id', $userId ?: auth()->id())
->delete();
}
public function subscriptions()
{
return $this->hasMany(ThreadSubscription::class);
}
public function getIsSubscribedToAttribute()
{
return $this->subscriptions()
->where('user_id', auth()->id())
->exists();
}
}
میتوانیم از کلاس تست که کاربری به نام Jeff ایجاد کرده است، استفاده کنیم. در اینجا کلاس NotificationsTest
است که از تمام تستهای لازم برای اعلانها پشتیبانی میکند.
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Notifications\DatabaseNotification;
use Tests\TestCase;
class NotificationsTest extends TestCase
{
use DatabaseMigrations;
public function setUp()
{
parent::setUp();
$this->signIn();
}
function test_a_notification_is_prepared_when_a_subscribed_thread_receives_a_new_reply_that_is_not_by_the_current_user()
{
$thread = create('App\Thread')->subscribe();
$this->assertCount(0, auth()->user()->notifications);
$thread->addReply([
'user_id' => auth()->id(),
'body' => 'Some reply here'
]);
$this->assertCount(0, auth()->user()->fresh()->notifications);
$thread->addReply([
'user_id' => create('App\User')->id,
'body' => 'Some reply here'
]);
$this->assertCount(1, auth()->user()->fresh()->notifications);
}
function test_a_user_can_fetch_their_unread_notifications()
{
create(DatabaseNotification::class);
$this->assertCount(
1,
$this->getJson("/profiles/" . auth()->user()->name . "/notifications")->json()
);
}
function test_a_user_can_mark_a_notification_as_read()
{
create(DatabaseNotification::class);
tap(auth()->user(), function ($user) {
$this->assertCount(1, $user->unreadNotifications);
$this->delete("/profiles/{$user->name}/notifications/" . $user->unreadNotifications->first()->id);
$this->assertCount(0, $user->fresh()->unreadNotifications);
});
}
}
توجه داشته باشید که باید فکتوری مدل خود را نیز بروزرسانی کنیم. در اینجا، ما همه چیز را در فایل UserFactions.php
تعریف کردیم.
<?php
use Faker\Generator as Faker;
$factory->define(App\User::class, function (Faker $faker) {
static $password;
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('secret'),
'remember_token' => str_random(10),
];
});
$factory->define(App\Thread::class, function ($faker) {
return [
'user_id' => function () {
return factory('App\User')->create()->id;
},
'channel_id' => function () {
return factory('App\Channel')->create()->id;
},
'title' => $faker->sentence,
'body' => $faker->paragraph
];
});
$factory->define(App\Channel::class, function ($faker) {
$name = $faker->word;
return [
'name' => $name,
'slug' => $name
];
});
$factory->define(App\Reply::class, function ($faker) {
return [
'thread_id' => function () {
return factory('App\Thread')->create()->id;
},
'user_id' => function () {
return factory('App\User')->create()->id;
},
'body' => $faker->paragraph
];
});
$factory->define(\Illuminate\Notifications\DatabaseNotification::class, function ($faker) {
return [
'id' => \Ramsey\Uuid\Uuid::uuid4()->toString(),
'type' => 'App\Notifications\ThreadWasUpdated',
'notifiable_id' => function () {
return auth()->id() ?: factory('App\User')->create()->id;
},
'notifiable_type' => 'App\User',
'data' => ['foo' => 'bar']
];
});
اجرای تست نشان میدهد که این کد به درستی کار میکند.
[email protected]:~/Code/forumio$ phpunit --filter NotificationsTest
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 1.13 seconds, Memory: 10.00MB
OK (3 tests, 6 assertions)
علاوه بر این، میتوانیم این کد را در مرورگر نیز تست کنیم. ما یک موضوع یا thread داریم که کاربری به نام Nikola Tesla در آن مشترک است. ما میخواهیم به عنوان یک کاربر متفاوت وارد سیستم شده و پاسخی را برای آن موضوع ارسال کنیم. هنگامی که این اتفاق میافتد، پس ما باید یک اعلان جدید در پایگاه داده مشاهده کنیم.
پس از پاسخ داده شده فوق، جدول اعلانهای پایگاه داده را نیز بررسی میکنیم تا ببینیم که یک اعلان جدید ایجاد شده است یا خیر.
ارائه اعلانها با VueJS
ما اعلان را در نوار پیمایش در سمت راست ارائه میکنیم. این کامپوننت فقط برای ورود به سیستم کاربر ارائه میشود، بنابراین شما میتوانید نشانه گذاری مشابه آن را در nav.blade.php
اضافه کنید.
<!-- Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">
<!-- Authentication Links -->
@guest
<li><a href="{{ route('login') }}">Login</a></li>
<li><a href="{{ route('register') }}">Register</a></li>
@else
<user-notifications></user-notifications>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true">
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{ route('profile', Auth::user()) }}">My Threads</a></li>
<li>
<a href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
Logout
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
</li>
</ul>
</li>
@endguest
</ul>
اکنون میخواهیم یک کامپوننت جدید Vue بسازیم، ما باید یک watcher را نیز به صورت زیر راه اندازی کنیم.
[email protected]:~/Code/forumio$ yarn run watch-poll
yarn run v1.3.2
$ node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --watch-poll --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js
این کامپوننت جدید <user-notifications>
را به عنوان یک کامپوننت سراسری در app.js
رجیستر میکنیم.
require('./bootstrap');
window.Vue = require('vue');
Vue.component('flash', require('./components/Flash.vue'));
Vue.component('paginator', require('./components/Paginator.vue'));
Vue.component('user-notifications', require('./components/UserNotifications.vue'));
Vue.component('thread-view', require('./pages/Thread.vue'));
const app = new Vue({
el: '#app'
});
اکنون میتوانیم فایل کامپوننت را به صورت زیر ایجاد کنیم.
UserNotifications.vue
کد موجود در کامپوننت به صورت زیر خواهد بود:
<template>
<li class="dropdown" v-if="notifications.length">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-bell"></span>
</a>
<ul class="dropdown-menu">
<li v-for="notification in notifications">
<a :href="notification.data.link"
v-text="notification.data.message"
@click="markAsRead(notification)"
></a>
</li>
</ul>
</li>
</template>
<script>
export default {
data() {
return {notifications: false}
},
created() {
axios.get('/profiles/' + window.App.user.name + '/notifications')
.then(response => this.notifications = response.data);
},
methods: {
markAsRead(notification) {
axios.delete('/profiles/' + window.App.user.name + '/notifications/' + notification.id)
}
}
}
</script>
ما با کاربر Nikola Tesl در یک موضوع از The Weather is Beautiful Outside مشترک شده بودیم. سپس در یک جلسه متفاوت، ما به عنوان یک کاربر متفاوت به نام Tom وارد شدیم و یک پاسخ جدید قرار دادیم. این بدان معناست که نیکولا تسلا باید از فعالیت جدیدی در این موضوع مطلع شود. آیا این عمل به درستی کار میکند؟
میتوانیم ببینیم که وقتی به عنوان نیکولا تسلا وارد میشویم، یک اعلان جدید در نوار نمایش داده میشود که لینکی مستقیم با موضوع مورد نظر دارد.
مطالعه مقالات بیشتر در لیداوب:
در این آموزش از لیداوب یاد گرفتیم که چگونه برای اولین بار به کاربران امکان اشتراک در یک موضوع را بدهیم و همچنین فرا گرفتیم که چگونه کاربرانی که در یک موضوع خاص مشترک هستند را تنظیم کرده و برای آنها نوتیفیکیشن ارسال کنیم. با سایر مقالات ما همراه باشید.
دیدگاه ها
متاسفانه فقط اعضای سایت قادر به ثبت دیدگاه هستند
ورود به سایت