Laravel scheduler cron options from database - laravel

I am new to laravel.
I am finding how can i execute schedules.
The schedule parameters (frequency, command) are stored in a mysql table.
I have thought a solution which is:
Use file_put_contents to write in app/Console/Kernel.
Any better idea would be welcome.
Thanks in advance,
Chris Pappas

The frequency of a certain command is defined on multiple levels. The Cron entry on your server calls the schedular every minute. In your scheduler there are frequency options as well.
One of the possibilities to let paramaters that are stored in the database define the frequency of command executions is to add a executed_at field to the table and a local scope to your Commands model that validates the schedule parameters in your mysql table:
public function scopeReadyToExecute($query)
{
return $query->where('executed_at', '<=', Carbon::now()->subMinutes(5)->toDateTimeString());
}
In this example the frequency is 5 minutes, of course you have to change this value according the the value in the frequency field.
Now, you could for example define master:command in Kernel.php to be executed every minute:
$schedule->command('master:command')->everyMinute()
In the handle of master:command you call the scope function to get the commands that are ready to execute:
public function handle()
{
$commands = Command::readyToExecute()->get();
foreach ($commands as $command) {
$this->call($command->name)
}
}
Each command that is ready to execute according to the parameters defined in your table will be executed.
Good luck!

Here in my solution.
app/Console/Kernel.php
$report_schedules = ReportSchedule::all();
foreach ($report_schedules as $report_schedule) {
$method_name = (string)$report_schedule->report->method_name;
$schedule->call(function () use ($method_name, $report_schedule) {
$emailSchedules = new EmailSchedules();
$email_List=array_unique(array_merge($report_schedule->users()->get()->lists("email")->toArray(), $report_schedule->groups()->with(["users"=>function ($query){$query->select("email");}])->get()->toArray()));
$users=\App\User::whereIn("email",$email_List)->get(["first_name","last_name","email"]);
$users_to=[];
$emails_to=[];
foreach($users as $user){
array_push($users_to,$user->first_name." ".$user->last_name);
array_push($emails_to,$user->email);
}
$emailSchedules->$method_name($emails_to,$users_to);
})->cron($report_schedule->frequency)->name('mail')->timezone("Europe/Athens");
}
Hope that help someone in the future.

Related

Why laravel dispatcer dispatch() function not creating jobs in jobs table of particular database?

I have multiple databases and switching database as per some conditions before dispatching jobs. For eg:
if(condition A){
DB::setDefaultConnection('A');
} else {
DB::setDefaultConnection('B');
}
dispatch(new CreateNewUser($user))
And the db connection is changed as per the condition.
As I query something like User::find($id) before dispatching the job which returns data as per the connected database.
But the problem is, the dispatch() create a jobs record in jobs table of that database which was connected at first. When the db connection changed to another, still the first database's jobs table is populated.
How is this happening?
Based on the comment, do the following, purge and reconnect after your change:
DB::purge('mysql');
//Do whatever you need to do to setup your connection
DB::setDefaultConnection('B');
DB::reconnect('mysql');
The job you are dispatching runs async. That DB change you are performing is not performed inside the Job. You should send the connection name as a parameter to the Job:
dispatch(new CreateNewUser($user, $connName))
and on the Job initialization, do:
protected $connName;
public function __construct($user, $connName)
{
$this->connName = $connName;
}
public function handle()
{
DB::setDefaultConnection($this->connName);
....
}

how to save data in the background every 2 minutes in Laravel

we're making a web app that's supposed to save the patient's viral signs history in the database, like every two minutes or so in the background. is it possible to do that in laravel if so how can i do it?
I recommend using job:
php artisan make:job PatientViralSignsJob
this command will generate class PatientViralSignsJob in app\Jobs folder.
in the PatientViralSignsJob ... hande() method you could write your logic
public function handle()
{
// write your code here ...
}
finally register your job class in Console\Kernel.php in schedule method:
protected function schedule(Schedule $schedule)
{
$schedule->job(new PatientViralSignsJob())->everyFiveMinutes();
}
this will make your handle method in PatientViralSignsJob executed every five minutes
more details in:
https://laravel.com/docs/7.x/queues#creating-jobs

How to notify the admin if the items(Multiple items) are going to expire in days later-Laravel

There are many items in the Items table and there is an expired date column in the items table. I just want to get a push notification every day before each items going expire one date before (Expire dates are different to each other and can have one expire date to multuple items). im new to laravel. Please help me.
This can be simply done with Laravel task scheduling
You need to create a custom artisan command like
php artisan make:command SendItemExpiryEmailsToAdmin
Under App\Console\Commands You will find a class SendItemExpiryEmailsToAdmin should be created.
i) First you need to define the signature of the command which will be used to call from the command line.
protected $signature = 'email:send-item-expiry-email-to-admin';
ii) Inside the handle() of the same class, write your logic. A sample logic is given below. You will need to create a Mailable class to send the mail.
public function handle() {
// To fetch all the items ids which are going to expired today.
$itemsIds = Items::whereBetween('expired',
[Carbon::now()->setTime(0,0)->format('Y-m-d H:i:s'),
Carbon::now()->setTime(23,59,59)->format('Y-m-d H:i:s')])
->pluck('id')
->toArray();
$itemsIds = implode(" ", $itemsIds);
Mail::queue(new ItemsExpiryEmail($itemsIds));
// If you are not using queue, just replace `queue` with `send` in above line
}
Mailable to send the mail.
i) Run the following command to create a mailable
php artisan make:mail ItemsExpiryEmail
ii) In the mailable, write your code. A sample code is given below, you can use $this->itemIds in the mail view as it is a public variable.
class ItemsExpiryEmail extends Mailable
{
use Queueable, SerializesModels; // don't forget to import these.
public $itemIds;
public function __construct($itemIds)
{
$this->itemIds = $itemIds;
}
public function build()
{
return $this->view('emails.orders.shipped');
return $this->to('test#example.com', 'Admin')
->subject('Subject of the mail')
->view('emails.adminNotification'); // adminNotification will be the view to be sent out as email body
}
}
This command needs to be executed daily using the Cornjob.
Try this, I'm sure this will help. Let me know if you have any questions.
You can create Task scheduler in Laravel. you can get more info from it's doc here: https://laravel.com/docs/5.8/scheduling
1) you get create login in the scheduling code like. get the all the items which is expiring tomorrow, And send the detail of items to the admin email.
if you have some knowledge in user define artisan command you can also implement scheduling-artisan-commands https://laravel.com/docs/5.8/scheduling#scheduling-artisan-commands.

Laravel: Check if new items added to db table and schedule a job to email user

I want to trigger an email when new rows are added to a table in my Laravel application. However I want to add a buffer of sorts, so if 5 rows are added in quick succession then only 1 email is sent.
The method I've chosen is to schedule a check every 15 minutes and see if there are new rows added. If there are then I will queue an email.
Currently I'm getting an error on the schedule. I'll run through my code below:
In Kernel.php where we setup schedules I have:
$schedule->job(new ProcessActivity)
->everyFifteenMinutes()
->when(function () {
return \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(15), Carbon::now()))->exists();
})
->onSuccess(function () {
Log::debug(
'Success'
);
})
->onFailure(function () {
Log::debug(
'Fail'
);
});
Which I use to trigger the Job found in: App\Jobs\ProcessActivity.php :
public function __construct()
{
$this->jobs = \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(15), Carbon::now()))->get();
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
Log::debug('Activity Job Run', ['jobs' => $this->jobs]);
$this->jobs->each(function ($item, $key) {
Log::debug('loop');
// get project
$project = $item->project;
// get project email
$user_id = $project->user_id;
$email = \App\User::find($user_id)->email;
// get project UUID
$projectUuid = $project->public_id;
// emails
$subscriberEmails = \App\ProjectSubscription::where('project_id', $project->id)->get();
// create activity email
Notification::route('mail', $subscriberEmails)->notify(new Activity($project, $projectUuid));
});
return true;
}
I've posted my full code above which also shows a relationship between my JobItems and Project models. I won't elaborate on that as I've commented in the code.
The problem
When I add a new row to my JobItem table I can see the job is scheduled and processed (using Laravel Telescope to inspect this).
However, I can also see in my log that for each job I get two log messages:
First: 'Fail' and then 'Activity Job Run'
My email is not sent and I'm uncertain how to determine why this is failing.
So it seems that onFailure is being triggered and there is a problem with my ProcessActivity.
Any clues on where I am going wrong and how to determine the error would be much appreciated.
I have a fix, but first, here are some things I learnt that hampered my progress:
I was using this artisan command to process my scheduled jobs:
php artisan queue:work
The problem with developing while using that command is that if there are code changes then those changes are not recognised.
So you can either Command+C to return to the console and use this every time there is a code change:
php artisan queue:restart
php artisan queue:work
Or you can just use this and it will allow code changes:
php artisan queue:listen
As you can imagine without knowing this you will have a slow debugging process!
As a result of this and adding an exception to my Job I made some progress. I'll paste in the code below to compare against the original code:
public function __construct()
{
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
try {
$jobs = \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(20), Carbon::now()))->get();
Log::debug('Activity Job', ['jobs' => $jobs]);
// collection start
$collection = collect();
// loop jobs to get emails
foreach ($jobs as $key => $value) {
// get project UUID
$project = $value->project;
$projectUuid = $project->public_id;
// get emails subscribed to projects
$subscriberEmails = \App\ProjectSubscription::where('project_id', $project->id)->get();
// merge into a single collection via the loop
if ($key != 0) {
$merge = $collection->merge($subscriberEmails);
$collection = collect($merge);
} else {
$collection = $subscriberEmails;
}
// Log::debug('emails_project in loop', ['emails' => $subscriberEmails]);
};
// clean object with uniques only
$subscriberEmailsCleaned = $collection->unique();
// debug
Log::debug('Project Emails to Notify', ['emails' => $subscriberEmailsCleaned]);
// create activity email
Notification::route('mail', $subscriberEmailsCleaned)->notify(new Activity($project, $projectUuid));
} catch (\Exception $e) {
\Log::info($e->getMessage());
}
}
First thing to note, is that as __construct() is run initially and is serialised. Then the handle method is called when the job is processed. So I had to move my eloquent query into the handle method.
I also used a foreach rather than .each to loop through and create a new collection of emails. Perhaps there is a more elegant way, but I needed to create a collection of emails and this way allowed me to move the variables in the loop outside to be used in the method.
You can see me merge these at the bottom of the loop.
I have also added a few Log:: items which is useful for debugging.
Not fixed 100%
With this code I can now auto schedule an email every x minutes when new items are added. However, I am still getting the log Fail from the onFailure()from my Kernal.php file:
->onFailure(function () {
Log::debug(
'Fail'
);
I am still confused as to what that indicates and how I can determine more information about how this has failed and what that means. However, it does work so I will cautiously move forward (with one eye open on the comments, in case someone has an idea that can help!)

laravel add scheduler dynamically

I have a system where the user can create background tasks via the UI.
The task interval are every few hours (user choice in the UI).
when the user creates a task via the ui i want to add it to the scheduler dynamically.
As the example states, this is static and not dynamic.
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
Is it possible? if not, what are the alternatives?
Thanks
I don't see why it wouldn't be possible. The Kernel::schedule method will be run every time php artisan schedule:run is run. If you set it up like the documentation, should be every minute via a cron.
* * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1
With that in mind, I don't see why you can't do something like this:
protected function schedule(Schedule $schedule)
{
// Get all tasks from the database
$tasks = Task::all();
// Go through each task to dynamically set them up.
foreach ($tasks as $task) {
// Use the scheduler to add the task at its desired frequency
$schedule->call(function() use($task) {
// Run your task here
$task->execute();
})->cron($task->frequency);
}
}
Depending on what you store, you can use whatever you like here instead of the CRON method. You might have a string stored in your database that represents one of Laravel's predefined frequencies and in which case you could do something like this:
$frequency = $task->frequency; // everyHour, everyMinute, twiceDaily etc.
$schedule->call(function() {
$task->execute();
})->$frequency();
The main thing to note here, is that the schedule isn't actually scheduling in tasks in the database or in a cron that it manages. Every time the scheduler runs (Every minute) it runs and it determines what to run based on the frequencies you give each task.
Example:
You have a task set up using ->hourly(), that is, to run on the hour, every hour.
At 00:00, the schedule runs, the ->hourly() filter passes, because the time is on the hour, so your task runs.
At 00:01, the schedule runs and but this time the ->hourly() filter fails, so your task does not run.

Resources