running a task every five minutes without overlapping? - laravel

Quoting https://laravel.com/docs/5.1/scheduling#preventing-task-overlaps ,
$schedule->command('emails:send')->withoutOverlapping();
In this example, the emails:send Artisan command will be run every
minute if it is not already running.
What if I wanted the task to run every five minutes instead? Could I do this?:
$schedule->command('emails:send')->everyFiveMinutes()->withoutOverlapping();

Yes you can do this. command returns an instance of Event the underlying code for Event has a fluent interface which allows you to chain these together in this way.
You can see this for yourself if you look at the withoutOverlapping method.
/**
* Do not allow the event to overlap each other.
*
* #return $this
*/
public function withoutOverlapping()
{
$this->withoutOverlapping = true;
return $this->skip(function () {
return file_exists($this->mutexPath());
});
}

Related

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

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!)

No scheduled commands are ready to run | only in $schedule

Laravel Version: 5.7.25
PHP Version: 7.2.14
Database Driver & Version: MySQL 2ยช gen. 5.7
Hi, Sorry for the trouble, I have this problem in creating a scheduled command.
Description:
In our crontab -e user we have inserted the following on Debian:
* * * * * cd /var/www/myfolder.com/ && php artisan schedule:run >> crontab.laravel
We have correctly entered the schedule function as follows:
app/console/kernel.php
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* #var array
*/
protected $commands = [
// 'App\Console\Commands\HelpCenter',
Commands\HelpCenter::class
];
/**
* Define the application's command schedule.
*
* #param \Illuminate\Console\Scheduling\Schedule $schedule
* #return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('elena:help')->everyMinute();
}
/**
* Register the commands for the application.
*
* #return void
*/
protected function commands()
{
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
}
but the result continues to be No scheduled commands are ready to run
we tried all the combinations impossible but nothing.
If we uncomment the function Artisan call working, the script is executed
Try to force command crontab -e
To better understand where the problem lies We have added a crontab directly with the command without going through the schedule as :
* * * * * cd /var/www/myfolder.com/ && php artisan elena:help >> crontab.laravel
This command runs perfectly every minute so we can not figure out where the problem is.
if you have the patience to give us some advice we will be happy. good day
I copy this here because this solved my issue. It's #piscator comment above.
"Did you run php artisan cache:clear? There might be a schedule file cached in your storage/framework folder."
EDIT:
This issue was giving me a lot of trouble. I'm using Laravel 5.8 and I couldn't make it work with ->withoutOverlapping(); The task will die after a while without apparent reasons and then the cron job couldn't start it again because ->withoutOverlapping() was preventing it.
Finally I found a solution here:
Basically, instead of using Laravel's ->withoutOverlapping(), you check yourself if the task is already running.
// start the queue worker, if its not running
$command = "queue:work --queue=high,email,default,low --tries=3 --delay=3";
if (!$this->osProcessIsRunning($command)) {
$schedule->command($command)->everyMinute()
->runInBackground()
->appendOutputTo(storage_path('logs/queue.log'));
}
// function for checking if the task is running
protected function osProcessIsRunning($needle)
{
// get process status. the "-ww"-option is important to get the full output!
exec('ps aux -ww', $process_status);
// search $needle in process status
$result = array_filter($process_status, function($var) use ($needle) {
return strpos($var, $needle);
});
// if the result is not empty, the needle exists in running processes
if (!empty($result)) {
return true;
}
return false;
}
Thanks to #henryonsoftware for the solution
I don't know if I am late, but I have a solution for you.
JUST ADD THE TIMEZONE IN THE SCHEDULE METHOD.
it will start working well.
Example:
$schedule->command('tenam:before')
->dailyAt('22:28')->timezone('Asia/Kolkata');

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.

Laravel scheduler cron options from database

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.

Resources