در حال بارگزاری ...

تنظیم اعلانات برای کاربران مشترک (Subscribed Users) در لاراول

توسط سميه شفيعي
آخرین به روز رسانی شنبه 16 آذر 1398

در این آموزش از لیداوب، چگونگی تنظیم اعلان‌ها یا نوتیفیکیشن‌ها را برای کاربران مشترک (Subscribed Users) در لاراول را بررسی خواهیم کرد. برای یادگیری بیشتر این موضوع با ما همراه باشید.

به عنوان مثال، هر زمان که یک موضوع (thread) پاسخ جدیدی را دریافت می‌کند، می‌توانیم به طور خودکار یک ایمیل ارسال کنیم یا یک کامپوننت نوتیفیکیشن را در خود وب سایت وارد کنیم. 

تنظیم اعلانات برای کاربران مشترک (Subscribed Users) در لاراول

ما می‌خواهیم از ویژگی‌های Notifiable پیش‌فرض در لاراول که برای تنظیم انواع اعلانات استفاده می‌شود، بهره ببریم. می‌توان از ابزار آرتیسان در لاراول، برای این کار استفاده کرد.

vagrant@homestead:~/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

 

 بنابراین، ما این دستور را اجرا کرده و مایگریشن‌های پایگاه داده را نیز ایجاد می‌کنیم:

vagrant@homestead:~/Code/forumio$ php artisan notifications:table

Migration created successfully!

vagrant@homestead:~/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 به صورت زیر ایجاد می‌کنیم.

 

vagrant@homestead:~/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', 'HomeController@index')->name('home');

Route::get('/threads', 'ThreadsController@index');
Route::get('/threads/create', 'ThreadsController@create');
Route::get('/threads/{channel}/{thread}', 'ThreadsController@show');
Route::delete('/threads/{channel}/{thread}', 'ThreadsController@destroy');
Route::post('/threads', 'ThreadsController@store');
Route::get('/threads/{channel}', 'ThreadsController@index');
Route::get('/threads/{channel}/{thread}/replies', 'RepliesController@index');
Route::post('/threads/{channel}/{thread}/replies', 'RepliesController@store');
Route::patch('/replies/{reply}', 'RepliesController@update');
Route::delete('/replies/{reply}', 'RepliesController@destroy');

Route::post('/threads/{channel}/{thread}/subscriptions', 'ThreadSubscriptionsController@store')->middleware('auth');
Route::delete('/threads/{channel}/{thread}/subscriptions', 'ThreadSubscriptionsController@destroy')->middleware('auth');

Route::post('/replies/{reply}/favorites', 'FavoritesController@store');
Route::delete('/replies/{reply}/favorites', 'FavoritesController@destroy');

Route::get('/profiles/{user}', 'ProfilesController@show')->name('profile');
Route::get('/profiles/{user}/notifications', 'UserNotificationsController@index');
Route::delete('/profiles/{user}/notifications/{notification}', 'UserNotificationsController@destroy');

این بدان معناست که ما به یک کنترلر جدید به نام UserNotificationsController با متدهای index()  و destroy()   نیاز داریم.

 

vagrant@homestead:~/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 را ایجاد کنیم تا فایل‌های مورد نیاز را به صورت خودکار بسازیم.

 

vagrant@homestead:~/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']
    ];
});

اجرای تست نشان می‌دهد که این کد به درستی کار می‌کند.

 

vagrant@homestead:~/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 را نیز به صورت زیر راه اندازی کنیم.

vagrant@homestead:~/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 وارد شدیم و یک پاسخ جدید قرار دادیم. این بدان معناست که نیکولا تسلا باید از فعالیت جدیدی در این موضوع مطلع شود. آیا این عمل به درستی کار می‌کند؟

تنظیم اعلانات برای کاربران مشترک

 می‌توانیم ببینیم که وقتی به عنوان نیکولا تسلا وارد می‌شویم، یک اعلان جدید در نوار نمایش داده می‌شود که لینکی مستقیم با موضوع مورد نظر دارد.

مطالعه مقالات بیشتر در لیداوب:

 در این آموزش از لیداوب یاد گرفتیم که چگونه برای اولین بار به کاربران امکان اشتراک در یک موضوع را بدهیم و همچنین فرا گرفتیم که چگونه کاربرانی که در یک موضوع خاص مشترک هستند را تنظیم کرده و برای آن‌ها نوتیفیکیشن ارسال کنیم. با سایر مقالات ما همراه باشید.

 

دیدگاه ها

دیدگاه ها : 0


متاسفانه فقط اعضای سایت قادر به ثبت دیدگاه هستند

رایگان

اشتراک گذاری در
ثبت امتیاز
5 (1 رای)

   لطفا صبر کنید ...