Laravel: Notifications table with custom column - php

I am using Laravel's default notifications system. However, I need an extra column in the notifications table to check if the user already has the same unread-notification or not.
For example: If user's profile is not complete then on every login he/she will be reminded until the profile is complete. But if the previously generated notification is still unread then the new notification will not be generated.
Table: notifications
default_fields
notify
...
...
profile_incomplete
...
...
password_change_overdue
...
Notifications class
class NotifyToCompleteProfileNotification extends Notification
{
public function __construct()
{
$this->notify = 'profile_incomplete';
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['database'];
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
'data' => '...',
];
}
}

You can use custom notification channel to make this works. Let's assume you want to add a group field to notifications. First, add this field to table, then make file GroupedDbChannel.php:
namespace App\Notifications;
use Illuminate\Notifications\Notification;
class GroupedDbChannel
{
public function send($notifiable, Notification $notification)
{
$data = $notification->toDatabase($notifiable);
return $notifiable->routeNotificationFor('database')->create([
'id' => $notification->id,
'group' => $notification->group,
'type' => get_class($notification),
'data' => $data,
'read_at' => null,
]);
}
}
Next, you need to define custom group and channel for notification:
namespace App\Notifications;
use Illuminate\Notifications\Notification;
class TestNotification extends Notification {
/*
* Here you define additional value that will
* be used in custom notification channel.
*/
public string $group = 'incomplete-profile';
public function via($notifiable) {
return [GroupedDbChannel::class];
}
public function toArray($notifiable) {
return [
// notification data
];
}
/*
* It's important to define toDatabase method due
* it's used in notification channel. Of course,
* you can change it in GroupedDbChannel.
*/
public function toDatabase($notifiable): array
{
return $this->toArray($notifiable);
}
}
And it's done. Use notifications as standard.
$user->notify(new TestNotification());
Now, value incomplete-profile from $group field goes to notifications table to group column.

Related

Laravel notification with one notification class

In my web app I have added the notification functionalities.
There are 3 notification class.
App/Notifications/
NotifWhenLiked.php
NotifyWhenStoryCommented.php
NotifyWhenAuthorFollowed.php
I want to make these task with one single Notification class. Is there any easiest way to solve this ?
Here is the code of one class
<?php
namespace App\Notifications;
use App\Http\Resources\Users;
use App\Model\User;
use App\Model\Story;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class NotifyWhenLiked extends Notification implements ShouldQueue
{
use Queueable;
public $user;
public $story;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(Story $story, User $user)
{
$this->user = $user;
$this->story = $story;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['database','broadcast'];
}
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toDatabase($notifiable)
{
return [
'notification' => "<strong>".$this->user->name."</strong>". ' liked your story '. "<strong>".$this->story->title."</strong>",
'Storylink' => '/story/'.$this->story->url_key,
'Userlink' => '/a/'.$this->user->profile->username
];
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
'notification' => $this->user->name. ' liked your story '. "<strong>".$this->story->title."</strong>",
'username' => $this->user->profile->username,
];
}
}
You might try to store the different notification types into an array e.g. a config file.
return [
'when_liked' => [
'notification_text' => '%s liked your story %s',
'story_link' => '/story/%s'
],
'when_commented' => [
'notification_text' => '%s commented on your story %s',
'story_link' => '/story/%s'
]
]
You can make a general Notification Class, which takes care about handling the notification stuff.
$whenLikedNotification = new YourCustomNotificationClass('when_liked');
$whenLikedNotification->trigger();
In the Constructor you can handle the config stuff.
I think you can use a event to do it
Create a event and call all listener(WhenLiked, WhenStoryCommented, WhenAuthorFollowed) that is related with event

Laravel use default email confirmation for guests

I have a question about my code for confirmation email guests. I have a comment system. When guest read a post, he can write a comment. When he add a comment, guest need confirm email (on comment form, I have field for email). How I can correctly do email confirmation? How In Laravel, when user register.
Now I have table "guests" and a model Guest, with columns: name, email, email_token and email_verified_at (how in users table).
In model guest I have function:
public function sendEmailNotification($token)
{
$this->notify((new ReviewEmailNotification($token)));
}
When comment is created, I call observed function created and send email:
public function created(Review $review)
{
Auth::guest() ? $review->sendEmailNotification($review->email_token) : '';
}
My notification:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Lang;
class ReviewEmailNotification extends Notification
{
use Queueable;
public $token;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject(Lang::getFromJson('Verify comment'))
->line(Lang::getFromJson('Please confirm your Email:'))
->action(Lang::getFromJson('Confirm E-Mail'), url(config('app.url') . route('revemail', $this->token)))
->line(Lang::getFromJson('...'));
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
My function for confirm email:
public function activateEmail($token = null)
{
$guest = Guest::where('email_token', $token)->first();
if ($review) {
$guest->email_verified_at = now();
$guest->save();
}
return redirect()->route('home');
}
And when email is verified I create record on guests table.
This solution is working. But how I can go away from email_token? In users table I have only email_verified_at without column token. And I want do token with expire date, how in laravel. Can I use default laravel email confirmation for guests?
Simple modify your sendEmailNotification currently which is accepting $token change it to email and in your function where you are using token change it to email like this:
open this function ReviewEmailNotification
modify like this Review::where('email',$email)
want more help then post the complete code of ReviewEmailNotification
finally i understand your query, you have to change the code.... Dont use the user table for that because in every post/article will publish new comment/review so guest table will be good and add post_id, user_id for email confirmation , so your new function look like this
public function created(Review $review)
{
Auth::guest() ? $review->sendEmailNotification($review-> post_id,$review->user_id,) : '';
}
public function activateEmail($token = null)
{
$guest = Guest::where('post_id', $post_id)->where('user_id', $user_id)->first();
if ($review) {
$guest->email_verified_at = now();
$guest->save();
}
return redirect()->route('home');
}
public function sendEmailNotification($user_id,$post_id)
{
$this->notify((new ReviewEmailNotification($post_id,$user_id )));
}

laravel 5.3 database notification customization

I am creating laravel 5.3 database notifications.I have created notifications as per video published on https://laracasts.com/series/whats-new-in-laravel-5-3/episodes/10 ,
Now i want to add custom fields to the notification table as per my requirements.
Please help me how to pass custom data to notification and access it.
When I needed to put custom fields to Notification, I'd just put on data field, as it is a Json field, works perfectly. Like this:
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
class TaskNotification extends Notification
{
use Queueable;
private $message;
/**
* #param String $message
*/
public function __construct($message=false)
{
if ($message)
$this->message = $message;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['database'];
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
'message' => $this->message,
'link' => route('mymodel.show'),
'task'=> 1, // This is one variable which I've created
'done'=> 0 // This is one variable which I've created
];
}
}

How to subscribe user with laravel notification channel for OneSignal

I am trying to use the laravel-notifications-channel/onesignal and I am having some problems with users in my laravel app set up to receive notifications. The documentation on the github page does not really cover how a user authenticates them self to receive a notification.
Even reading over the OneSignal docs for sending users to OneSignal is not working for me.
How do I set it up where when a user is using our web app they are notified to receive notifications and then I can send notifications to them using laravel notifications?
Here is my AssignedToTask Notification file:
<?php
namespace App\Notifications;
use App\Task;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use NotificationChannels\OneSignal\OneSignalChannel;
use NotificationChannels\OneSignal\OneSignalMessage;
use NotificationChannels\OneSignal\OneSignalWebButton;
class AssignedToTask extends Notification
{
use Queueable;
protected $task;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(Task $task)
{
//
$this->task = $task;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail', OneSignalChannel::class];
}
public function toOneSignal($notifiable)
{
return OneSignalMessage::create()
->subject("Your {$notifiable->service} account was approved!")
->body("Click here to see details.")
->url('http://onesignal.com')
->webButton(
OneSignalWebButton::create('link-1')
->text('Click here')
->icon('https://upload.wikimedia.org/wikipedia/commons/4/4f/Laravel_logo.png')
->url('http://laravel.com')
);
}
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject('You have been assigned a new task')
->line('You have a new task: ' . $this->task->title)
->action('View Task', url('tasks/' . $this->task->id));
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
In my user model:
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Zizaco\Entrust\Traits\EntrustUserTrait;
use HipsterJazzbo\Landlord\BelongsToTenants;
use Cmgmyr\Messenger\Traits\Messagable;
class User extends Authenticatable
{
use Notifiable;
use EntrustUserTrait;
use BelongsToTenants;
use Messagable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password', 'company_id'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token', 'company_id'
];
....
public function routeNotificationForOneSignal()
{
return 'ONE_SIGNAL_PLAYER_ID';
}
public function routeNotificationForMail()
{
return $this->email_address;
}
}
How do I set and get the ONE_SIGNAL_PLAYER_ID in user model so a user accepts notifications and I can send them notifications?
EDIT - 2
Since you don't know what's happening is, Let me try to explain how you can work with OneSignal.
This is a push messaging system just like any other push notification system. (FCM (google), PubNub).
How it Works
First goto OneSignal.Com Create your account, and then create an app for you. Once you create an app it will give you SDK for Mobile, where your consumers are.
Now whenever your consumers install and start your app, they will notify your web server with their own unique id and user information.
The information you received about the user is unique player_id which you will store in your database against that user.
Now when you want to send a notification to any mobile app just call the API Post Notification method with player_id and OneSignal will send a push notification to that mobile app.
EDIT - 1
I think now i understand your confusion about Notifications with OneSignalChannel
Flow
You already have the players_ids stored in your app database against every user.
Now when you want to push a notification to a player, you just take that users player_id from db and push a notification to OneSignal.
Well you took this meaning literally. Which was causing issue
public function routeNotificationForOneSignal()
{
return 'ONE_SIGNAL_PLAYER_ID';
}
From the error message this function should have return a unique id (UUID).
Change the return value to actual player id at OneSignalChannel
That's all my friend.

Laravel 5.3 - Single Notification for User Collection (followers)

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:
1k mails/sms sent (issue is not here)
1k notification entries added to the DB's notifications table
It 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:
Laravel would pick up the fact that it's an array notifiable_type
Save a single notification as 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?
Updated 2017-01-14: implemented more correct approach
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();
}
});
How to make it work?
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.

Categories