When I have a single notifiable
user, a single entry in the notifications
table is inserted, along with a mail
/sms
sent which is perfectly working via channels.
The issue is when I have a user
collection, a list of 1k users following me, and I post an update. Here is what happens when using the Notifiable
trait as suggested for multi-user case:
mails
/sms
sent (issue is not here)notifications
tableIt seems that adding 1k notifications to the DB's notifications
table is not an optimal solution. Since the toArray
data is the same, and everything else in the DB's notifications
table is the same for 1k rows, with the only difference being the notifiable_id
of the user
notifiable_type
.
An optimal solution out of the box would be:
array
notifiable_type
notifiable_type
user_array
or user
with notifiable_id
0 (zero would only be used to signify it's a multi notifiable user)Create/Use another table notifications_read
using the notification_id
it just created as the foreign_key
and insert 1k rows, of just these fields:
notification_id
notifiable_id
notifiable_type
read_at
I am hoping there is already a way to do this as I am at this point in my application and would love to use the built in Notifications and channels for this situation, as I am firing off emails
/sms
notifications, which is fine to repeat 1k times I think, but it's the entry of the same data into the database that is the problem that needs to be optimized.
Any thoughts/ideas how to proceed in this situation?
For example, if you are writing a billing application, you might send an "Invoice Paid" notification to your users via the email and SMS channels. In Laravel, each notification is represented by a single class that is typically stored in the app/Notifications directory.
Preview Of User Following: Step 1: Install Laravel 5.6 In first step, If you haven't installed laravel 5.6 in your system then you can run bellow command and get fresh Laravel project. Now we require to install laravel-follow package for like unlike system, that way we can use it's method. So Open your terminal and run bellow command.
By implementing the HasLocalePreference contract on your notifiable model, you may instruct Laravel to use this stored locale when sending a notification: * Get the user's preferred locale. Once you have implemented the interface, Laravel will automatically use the preferred locale when sending notifications and mailables to the model.
* The channels the user receives notification broadcasts on. Sending SMS notifications in Laravel is powered by Vonage (formerly known as Nexmo). Before you can send notifications via Vonage, you need to install the laravel/vonage-notification-channel and guzzlehttp/guzzle packages: The package includes a configuration file.
Quick example:
use Illuminate\Support\Facades\Notification; use App\Notifications\SomethingCoolHappen; Route::get('/step1', function () { // example - my followers $followers = App\User::all(); // notify them Notification::send($followers, new SomethingCoolHappen(['arg1' => 1, 'arg2' => 2])); }); Route::get('/step2', function () { // my follower $user = App\User::find(10); // check unread subnotifications foreach ($user->unreadSubnotifications as $subnotification) { var_dump($subnotification->notification->data); $subnotification->markAsRead(); } });
Step 1 - migration - create table (subnotifications)
use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateSubnotificationsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('subnotifications', function (Blueprint $table) { // primary key $table->increments('id')->primary(); // notifications.id $table->uuid('notification_id'); // notifiable_id and notifiable_type $table->morphs('notifiable'); // follower - read_at $table->timestamp('read_at')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('subnotifications'); } }
Step 2 - let's create a model for new subnotifications table
<?php // App\Notifications\Subnotification.php namespace App\Notifications; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\DatabaseNotification; use Illuminate\Notifications\DatabaseNotificationCollection; class Subnotification extends Model { // we don't use created_at/updated_at public $timestamps = false; // nothing guarded - mass assigment allowed protected $guarded = []; // cast read_at as datetime protected $casts = [ 'read_at' => 'datetime', ]; // set up relation to the parent notification public function notification() { return $this->belongsTo(DatabaseNotification::class); } /** * Get the notifiable entity that the notification belongs to. */ public function notifiable() { return $this->morphTo(); } /** * Mark the subnotification as read. * * @return void */ public function markAsRead() { if (is_null($this->read_at)) { $this->forceFill(['read_at' => $this->freshTimestamp()])->save(); } } }
Step 3 - create a custom database notification channel
Updated: using static variable $map to keep first notification id and insert next notifications (with the same data) without creating a record in notifications
table
<?php // App\Channels\SubnotificationsChannel.php namespace App\Channels; use Illuminate\Notifications\DatabaseNotification; use Illuminate\Notifications\Notification; class SubnotificationsChannel { /** * Send the given notification. * * @param mixed $notifiable * @param \Illuminate\Notifications\Notification $notification * * @return void */ public function send($notifiable, Notification $notification) { static $map = []; $notificationId = $notification->id; // get notification data $data = $this->getData($notifiable, $notification); // calculate hash $hash = md5(json_encode($data)); // if hash is not in map - create parent notification record if (!isset($map[$hash])) { // create original notification record with empty notifiable_id DatabaseNotification::create([ 'id' => $notificationId, 'type' => get_class($notification), 'notifiable_id' => 0, 'notifiable_type' => get_class($notifiable), 'data' => $data, 'read_at' => null, ]); $map[$hash] = $notificationId; } else { // otherwise use another/first notification id $notificationId = $map[$hash]; } // create subnotification $notifiable->subnotifications()->create([ 'notification_id' => $notificationId, 'read_at' => null ]); } /** * Prepares data * * @param mixed $notifiable * @param \Illuminate\Notifications\Notification $notification * * @return mixed */ public function getData($notifiable, Notification $notification) { return $notification->toArray($notifiable); } }
Step 4 - create a notification
Updated: now notification supports all channels, not only subnotifications
<?php // App\Notifications\SomethingCoolHappen.php namespace App\Notifications; use App\Channels\SubnotificationsChannel; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; class SomethingCoolHappen extends Notification { use Queueable; protected $data; /** * Create a new notification instance. * * @return void */ public function __construct($data) { $this->data = $data; } /** * Get the notification's delivery channels. * * @param mixed $notifiable * @return array */ public function via($notifiable) { /** * THIS IS A GOOD PLACE FOR DETERMINING NECESSARY CHANNELS */ $via = []; $via[] = SubnotificationsChannel::class; //$via[] = 'mail'; return $via; } /** * Get the mail representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { return (new MailMessage) ->line('The introduction to the notification.') ->action('Notification Action', 'https://laravel.com') ->line('Thank you for using our application!'); } /** * Get the array representation of the notification. * * @param mixed $notifiable * @return array */ public function toArray($notifiable) { return $this->data; } }
Step 5 - helper trait for "followers"
<?php // App\Notifications\HasSubnotifications.php namespace App\Notifications; trait HasSubnotifications { /** * Get the entity's notifications. */ public function Subnotifications() { return $this->morphMany(Subnotification::class, 'notifiable') ->orderBy('id', 'desc'); } /** * Get the entity's read notifications. */ public function readSubnotifications() { return $this->Subnotifications() ->whereNotNull('read_at'); } /** * Get the entity's unread notifications. */ public function unreadSubnotifications() { return $this->Subnotifications() ->whereNull('read_at'); } }
Step 6 - update your Users model
Updated: no required followers method
namespace App; use App\Notifications\HasSubnotifications; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; /** * Adding helpers to followers: * * $user->subnotifications - all subnotifications * $user->unreadSubnotifications - all unread subnotifications * $user->readSubnotifications - all read subnotifications */ use HasSubnotifications; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; }
Yes you are right i guess with the default Notifiable
trait, you could create a custom channel.
You can check the Illuminate\Notifications\Channels\DatabaseChannel
class for default creation and adopt it to a pivot-table one.
Hope this helps to create a new channel with a pivot table. Also, implement a HasDatabasePivotNotifications
trait (or similar name) to your own Notifiable
trait.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With