Laravel 5.4: Notification delay, only execute upon condition - laravel

It is a chat function, I would like to have the notification delay by 5 seconds, within (or after) these 5seconds the notification will only be sent if the specific message is being read during the time. I have researched for delay() or using sleep() in php lib but they can't seem to do the trick for the conditioning. Is it possible to do such thing?

use https://laravel.com/docs/5.4/notifications#queueing-notifications
class InvoicePaid extends Notification implements ShouldQueue
{
use Queueable;
// ...
}
$when = Carbon::now()->addMinutes(10);
$user->notify((new InvoicePaid($invoice))->delay($when));
You can check condition inside Notification

Related

Laravel broadcasting - use job ID as an 'order' property

I want to send some sort of (unique, auto-incrementing) number as part of the payload of an event - so that the consumer can, for example, know it should ignore an 'updated' event if the event is older than a previous 'update' event it received.
I see I can add a broadcastWith method to my event, where I could add such a number, which I'm storing in some table.
But, I don't really need to create a new number. The ID of the job in the jobs table will work just fine. So, how can I make Laravel automatically add a property, say order, to this event before it is broadcast and make the value of order to id column from the jobs table? Is there a way to get it in the broadcastWith method?
I had previously thought of using a timestamp as the 'order' but of course that won't help me or the consumer when two events have been created in a short-a timeframe as a computer can create two events.
UPDATE
Looks like I haven't worded it well and people are confused as to what I'm looking for. In hindsight, I shouldn't of added the criteria that it must be the job id that gets included in the payload. The main thing I'm after is a unique, auto-incrementing ID in each broadcast event. For example, I have an UserUpdated event. Say the a user is updated twice - my SPA that is consuming the events needs to know which event is the newer one, otherwise the SPA might display outdated info. If the events are delivered sequentially, then this problem won't happen. But, especially as I'm relying on a third-party service (Pusher) to deliver the events to the SPA, I don't want to assume / trust that the events will always be delivered in the same order they were sent to Pusher.
Hi such a nice requirement, i have been working on a POCO and here are some snippets, you do not need broadcast at all. Of course you need to have the queue worker up and running
Running The Queue Worker
On your order controller, i guess you need the update method to dispatch the job before commiting.
function update(Request $req)
{
$data= Order::find($req->id);
$data->amount=$req->amount; //example field
PostUpdateOrderJob::dispatch($data)->beforeCommit();
$data->save();
}
Important: Why before commit and not after commit: Setting the after_commit configuration option to true will also cause any queued event listeners, mailables, notifications, and broadcast events to be dispatched after all open database transactions have been committed.
Your PostUpdateOrderJob class
?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Order;
use Throwable;
class PostUpdateOrderJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* #var int
*/
public $tries = 25;
/**
* The maximum number of unhandled exceptions to allow before failing.
*
* #var int
*/
public $maxExceptions = 3;
protected $order;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(Order $order)
{
$this->order=$order;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$this->order->update(['jobid'=>$this->job->getJobId()]);
}
public function failed(Throwable $exception)
{
// Send user notification of failure, etc...
//Several options on link below
//https://laravel.com/docs/9.x/queues#dealing-with-failed-jobs
}
}
Well php has a function called time() which returns the current unix time in seconds, so you can just use that in your broadcast event.
In your broadcast event, you can add this time to the public class properties, which would then be available through the event payload:
class MyBroadcastEvent implements ShouldBroadcast
{
public $time;
public function __construct()
{
$this->time = time();
}
}
I'm not sure if this is exactly what you need, your question is kind of confusing to be honest.

Notification Testing no record created during tests in notifications table

I am trying to test if notification is created in the database, in my local, this works as intended however the test environment has some strange behaviour.
$user->notify(new ExampleNotification());
$this->assertDatabaseCount('notifications', 1);
//returns green with QUEUE_CONNECTION=sync but red with QUEUE_CONNECTION=database
...
class ExampleNotification extends Notification implements ShouldBroadcast, ShouldQueue
{
use Queueable;
public function viaQueues(): array
{
return [
'database' => 'notifications'
];
}
/**
* Get the notification's delivery channels.
*
* #return array
*/
public function via(User $notifiable): array
{
return ['database']
}
ps: I do not use Notification::fake() anywhere in my tests
Does anybody know why the different queue connections would behave differently?
Any queue driver other than the sync one works asynchronously i.e. when you send a notification job it will be queued and then the queue worker will remove it from the queue and then send the actual notification.
This means if you send a notification during a test, even if the queue worker is running the notification will most likely not be sent before the test is done.
To avoid this you should either use the sync queue or use Notification::fake() and just check if a notification would have been sent.

Laravel: Send 1000 emails every hour using queue

I have a table users with 5000 records (5000 users) and in my server i can just send 1000 emails every hour.
How i can send 1000 emails every hour using queue ?
or how to make queues sleep inside loop?
EmailController:
class EmailController extends Controller
{
public function sendEmail(Request $request){
$event=(object)['content' => "Hello Laravel fans",'subject' => 'Test Email'];
$users=User::all();
App\Jobs\SendReminderEmail::dispatch($users,$event)
}
}
SendReminderEmail
class SendReminderEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $event;
public $email;
public $users;
public function __construct($users,$event)
{
$this->users = $users;
$this->event = $event;
}
public function handle()
{
foreach ($this->users as $user) {
Mail::to($user->email)->queue(new Reminder($this->event));
}
}
}
Laravel has a neat feature which fits your case perfectly, it's called Task Scheduling, instead of making the job sleep for one hour, you could, instead, call it every hour.
To do so add the job schedule to the schedule() method located on App\Console\Kernel like this:
protected function schedule(Schedule $schedule)
{
$schedule->job(new SendReminderEmail)->hourly();
}
I also would recommend you to make the job self contained, that will make this task much simpler, I'm thinking in something like this:
class SendReminderEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $event;
public $users;
public function __construct()
{
$this->users = User::all();
$this->event = (object)['content' => "Hello Laravel fans",'subject' => 'Test Email'];;
}
public function handle()
{
foreach ($this->users as $user) {
Mail::to($user->email)->queue(new Reminder($this->event));
}
}
}
Now you can just get rid of your controller because this job will be executed every hour automatically.
Remember that you will need to run a cron on your server to check if the job needs to be executed. You can also run it manually if you want to test it using php artisan schedule:run.
Hope this helps you.
Laravel has a built-in throttle feature for rate-limited jobs. From the docs:
If your application interacts with Redis, you may throttle your queued
jobs by time or concurrency. This feature can be of assistance when
your queued jobs are interacting with APIs that are also rate limited.
For example, using the throttle method, you may throttle a given type
of job to only run 10 times every 60 seconds. If a lock can not be
obtained, you should typically release the job back onto the queue so
it can be retried later:
Redis::throttle('key')->allow(10)->every(60)->then(function () {
// Job logic...
}, function () {
// Could not obtain lock...
return $this->release(10);
});
In your case, that might look like Redis::throttle(...)->allow(1000)->every(3600)->...
If you're not using Redis, another possible solution which is specific to queued mail is to add a delay. Again, from the docs:
Delayed Message Queueing
If you wish to delay the delivery of a queued
email message, you may use the later method. As its first argument,
the later method accepts a DateTime instance indicating when the
message should be sent:
$when = now()->addMinutes(10);
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->later($when, new OrderShipped($order));
Using this method would require you to calculate the delay for each email during the loop that dispatches emails to the queue. For example, every 1000th iteration of the loop you could increase the delay by 1 hour.
If you're using the Amazon SQS queue service, the same maximum delay of 15 minutes that applies to other queued jobs might also apply here (I'm not sure on this part), in which case you would have to come up with another solution for checking if you're over your rate limit and releasing the job back to the queue.

Cannot get Laravel Mail::to()->later() to work

I'm trying to send a time-delayed email via a Redis queue and Mailgun using the following code...
$when = \Carbon\Carbon::now()->addMinutes(5);
Mail::to($demoDownloader->Email)->later($when, new DemoRequestFollowUp($demoDownloader));
I can see that the job is added to the redis queue (using Redis Desktop Manager) and stays in the queue for 5 minutes before disappearing from the queue. Unfortunately, it never appears in the Mailgun logs.
If I instead use this line of code...
Mail::to($demoDownloader->Email)->send(new DemoRequestFollowUp($demoDownloader));
...then the email appears in the Mailgun logs and subsequently arrives in the destination mailbox successfully (minus the time delay of course).
No error messages are written to storage/logs/laravel.log so I'm at a bit of a loss as to why this isn't working. I'm pretty sure I've used the syntax specified in the manual.
By the way, my mailable looks like this...
<?php
namespace App\Mail;
use \App\DemoDownloader;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class DemoRequestFollowUp extends Mailable
{
use Queueable, SerializesModels;
public $requestee;
public function __construct(DemoDownloader $requestee)
{
$this->requestee = $requestee;
}
public function build()
{
return $this->subject('Overview')
->view('email-demo-request-follow-up');
}
}
I'd be most grateful for any input.
I'd neglected to look in the failed_jobs table which gave me the clue I needed...
Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\DemoDownloader]
As the DemoDownloader instance being passed into the DemoRequestFollowUp constructor wasn't actually being used, I removed the argument and also removed the $requestee public property from the class and its assignment in the constructor. After making these changes emails were reliably arriving after the specified delay interval had passed.

Send email from queued event handler

I use Lumen 5.1 and Redis for queues. And I have a pretty standard event handler that should send an email:
<?php
namespace App\Handlers\Events;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Events\UserHasRegistered;
use Illuminate\Contracts\Mail\Mailer;
class SendWelcomeEmail implements ShouldQueue
{
protected $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function handle(UserHasRegistered $event)
{
$user = $event->user;
$this->mailer->raw('Test Mail', function ($m) use ($user) {
$name = $user->getFirstName().''.$user->getLastName();
$m->to($user->auth()->getEmail(), $name)->subject('This is a test.');
});
}
}
The email is sent when I don't use the ShouldQueue interface. But when I push the event handler to the queue (i. e. use the ShouldQueue interface), the email is not sent and I don't get any error messages.
Do you have any ideas how to solve or debug this?
It was not a bug, just an unexpected behaviour.
I am using Xampp on Windows and the php mail driver for development. For some reason the queued mails were not saved in the default mailoutput folder within the Xampp directory. Instead a new mailoutput folder was automatically created within the Lumen directory.
There I found all the missed mails. :)

Resources