I am struggling with dispatching a Slack notification when the Notification class implements ShouldQueue.
This is how I dispatch the notification
/**
* Handles the sendout of booking request confirmation to the customer
*
* #return void
*/
public function sendCustomerNotifications()
{
$this->booking->customer->notify((new CustomerBookingRequested($this->booking)));
}
This is how I my CustomerBookingRequested notification class looks like
class CustomerBookingRequested extends Notification implements ShouldQueue
{
use Queueable;
private $booking;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(Booking $booking)
{
//
$this->booking = $booking;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail','slack'];
}
...
//code for toMail
...
/**
* Get the Slack representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Message\SlackMessage
*/
public function toSlack($notifiable)
{
return (new SlackMessage)
->success()
->content('New booking requested!');
}
My Customer Model uses Notifiable
class Customer extends Model implements HasLocalePreference
{
use HasFactory;
use Billable;
use SoftDeletes;
use Notifiable;
...
I also added to my Customer Model the routing method
/**
* Route notifications for the Slack channel.
*
* #param \Illuminate\Notifications\Notification $notification
* #return string
*/
public function routeNotificationForSlack($notification)
{
return env('SLACK_WEBHOOK');
}
When I remove implements ShouldQueue from my Notification class, both Slack and Mail Message is sent. When I keep implements ShouldQueue, the Mail message is sent, Slack message is not sent.
I basically want to send the customer a mail notification with a booking confirmation. At the same time I want to send a Slack message to the team's slack workspace. That's why I just added a static webhook URL in the customer model which is linked to the company's slack workspace.
I am a bit stuck here. Probably it's something obvious, but I can't find what I do wrong.
Thanks for your support!
Using Laravel 8.0 with "laravel/slack-notification-channel": "^2.3"
Move the SLACK_WEBHOOK env variable to a config.
File: config/sample.php
return [
'url' => env('SLACK_WEBHOOK')
];
Then reference the config from the Customer model.
File: app/Models/Customer.php
public function routeNotificationForSlack($notification) {
return config('sample.url');
}
Related
So I am setting up a exception handler that send a Slack notification every time an exception happens in the application, I have also setup this when a Complaint(Model) is created and that work fine. But in my Exception/Handle.php I have this setup.
Notification::route('slack', env('LOG_SLACK_WEBHOOK_URL'))->notify(new ExceptionCreated($exception));
The webhook does exist and it does work. And while dd() every possible step I know that it hits the ExceptionCreated in Notifications. So there I have this code.
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\SlackMessage;
use Carbon\Carbon;
use Throwable;
use Auth;
use Illuminate\Support\Facades\Request;
class ExceptionCreated extends Notification
{
use Queueable;
/**
* #var Throwable
*/
public $exception;
/**
* Create a new notification instance.
*
* #param \Throwable $exception
* #return void
*/
public function __construct(Throwable $exception)
{
$this->exception = $exception;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['slack'];
}
/**
* Get the slack representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\SlackMessage
*/
public function toSlack($notifiable)
{
dd($this->exception);
//and a lot of code here but that doesn't matter
}
Now when I dump inside the constructor I can see the exception is being passed correctly, but it never fires the toSlack function and I have no idea why, I have cleared cache, dump autoload and nothing seems to be working, as I said I have this exact code for my Complaints and that works fine. So any suggestions are much appreciated.
I want tp apply queue for my Notifications, so I implemented that at my Notification which ResetPassword:
class ResetPassword extends Notification implements ShouldQueue
Then I ran php artisan queue:table and migrate it so the table jobs created successfully at the DB.
And also change the QUEUE_CONNECTION to database at .env file and re-run php artisan serve.
But when I test this and clicked on reset password link, a new table row must be added to jobs table but it does not.
And instead of that, this error returns:
ErrorException Undefined property:
App\Notifications\ResetPassword::$queue
...\notification\vendor\laravel\framework\src\Illuminate\Notifications\NotificationSender.php:195
So what is going wrong here ? How can I fix this issue ?
I would really appreciate any idea or suggestion from you guys...
Thanks in advance.
UPDATE #1:
ResetPassword.php:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
class ResetPassword extends Notification implements ShouldQueue
{
/**
* The password reset token.
*
* #var string
*/
public $token;
/**
* The callback that should be used to build the mail message.
*
* #var \Closure|null
*/
public static $toMailCallback;
/**
* Create a notification instance.
*
* #param string $token
* #return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Get the notification's channels.
*
* #param mixed $notifiable
* #return array|string
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
}
return (new MailMessage)
->subject('subject goes here')
->line('This email is sent to you')
->action(Lang::get('Reset Password'), url(config('app.url').route('password.reset', ['token' => $this->token, 'email' => $notifiable->getEmailForPasswordReset()], false)))
->line(Lang::get('Until the next 60 minutes you can use this link', ['count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
->line(Lang::get('If you did not request a password reset, no further action is required.'));
}
/**
* Set a callback that should be used when building the notification mail message.
*
* #param \Closure $callback
* #return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}
Look Like you have forgot to use Queueable Trait in your notification
use Illuminate\Bus\Queueable;
class ResetPassword extends Notification implements ShouldQueue
{
use Queueable;
Queueable trait has propety $queue
Ref:https://laravel.com/docs/8.x/notifications#queued-notifications-and-database-transactions
I'm new to Laravel. I just created a custom login with laravel 5.7. When I tried to reset password I'm getting this error:
"Declaration of
App\Employee::sendEmailVerificationNotification($token) should be
compatible with
Illuminate\Foundation\Auth\User::sendEmailVerificationNotification()"
Does anyone know how to resolve this error?
You may do something like this
class Employee extends Model implements MustVerifyEmail {
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail);
}
}
if you want to call it like Employee::sendEmailVerificationNotification() and if you want to verify the token you should extend the VerifyEmail notification something like
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Lang;
use Illuminate\Auth\Notifications\VerifyEmail;
class VerifyEmailNotification extends VerifyEmail
{
use Queueable;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($token)
{
//verify 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)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable);
}
return (new MailMessage)
->subject(Lang::getFromJson('Verify Email Address'))
->line(Lang::getFromJson('Please click the button below to verify your email address'))
->action(
Lang::getFromJson('Verify Email Address'),
$this->verificationUrl($notifiable)
)
->line(Lang::getFromJson('If you did not create an account, no further action is required.'));
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
Then in Employee model
public function sendEmailVerificationNotification($token)
{
$this->notify(new VerifyEmailNotification($token)); // your custom notification
}
You have to follow the same method signature if you want to override it-
You are overriding this method-
Illuminate\Foundation\Auth\User::sendEmailVerificationNotification()
to this-
App\Employee::sendEmailVerificationNotification($token)
If you notice the difference, you have passed $token in the method while the original method definition does not support that.
Create a different method if you need a different signature from the original method.
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.
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.