I have profiles that run on a schedule, in the settings of each profile there is a period of everyFiveMinutes type, Then in the kernel I use this
$tasks = ProfileExchange::where('active', true)->whereNotNull('frequency')->get();
foreach ($tasks as $task) {
$frequency = $task->frequency;
$schedule->command(RunExchange::class, ['--id' => $task->id])
->runInBackground()
->$frequency();
}
but if in setup I want to use this at('13:00') then I get an error, help me fix it
Your code won't work because a value of at('13:00') in $task->frequency will become this in your code:
$schedule->command(RunExchange::class, ['--id' => $task->id])
->runInBackground()
->at('13:00')();
It's a bad idea to run code from your database like this anyway, it opens you up to potential code injection.
If I were you, I'd change $task->frequency to be either "every 5 minutes", "every 10 minutes" and other similar options, then if-else or switch-case for them in your code:
if($task->frequency == "every 5 minutes") {
$schedule->command(RunExchange::class, ['--id' => $task->id])
->runInBackground()
->everyFiveMinutes();
} else if
...
Then you can do a regex check for whether $task->frequency is a time, and use at to run it at that time:
...
else if(preg_match("\d?\d:\d\d", $task->frequency)) {
$schedule->command(RunExchange::class, ['--id' => $task->id])
->runInBackground()
->at($task->frequency);
}
You'll need to change how you store the time in your database too, it will have to be 13:00 instead of at('13:00')
Related
The background
I am building a Laravel application and I have an upsert method on a Booking Controller for updating/inserting bookings.
On upsert.blade.php I want to display a <select> element with a list of days into which a booking can be moved (or inserted).
There is a 'holidays' table with only one column: 'day' (of type datetime, precision 6). Each entry on this table means the system will be on holidays for that day, so bookings cannot be made or transfered into days that appear on this table.
Now, I want the <option>s in the above mentioned <select> to be disabled when they correspond to a holiday.
What I tried:
The view (upsert.blade.php)
<select>
<option value="" disabled selected>Select</option>
#foreach($days as $day)
<option value="{{ $day['value'] }}" #disabled($day['disabled'])>
{{ $day['display'] }}
</option>
#endforeach
</select>
The controller action:
public function upsert()
{
$now = Carbon::now();
$last = Carbon::now()->addDays(30);
$holidays = DB::table('holidays');
$days = [];
// Populate $days with dates from $now until $last
while($now->lte($last))
{
array_push($days, [
'value' => $now->toDateString(),
'display' => $now->format('l j F Y'),
/*
* Mark day as disabled if holidays matching current
* day is greater than 1
* DOESN'T WORK
*/
'disabled' => $holidays->whereDate('day', $now)->count()
]);
$now->addDay();
}
return view('upsert', [
'days' => $days,
]);
}
The problem
The line labelled 'DOESN'T WORK' doesn't work as expected (I expect the query to return 1 if there is a holiday for the current day in the loop, thus marking the day as disabled). It only matches the first day of the loop if it's a holliday, but it won't match any other days.
Note: I have cast the 'day' property of the Holiday model to 'datetime' so Laravel casts the value to a Carbon object when accessing it.
Attempts to solve it
I tried replacing
$holidays = DB::table('holidays');
with
$holidays = Holiday::all();
but that throws the following exception
Method Illuminate\Database\Eloquent\Collection::whereDate does not exist.
So I tried rewriting the query to (note whereDate was replaced by where):
'disabled' => $holidays->where('day', $now->toDateString().' 00:00:00.000000')->count()
But this would never match
The solution
After around 6 hours of fiddling about with this line, reading Laravel documentation and talking to ChatGPT, I couldn't come up with an answert to why this is happening so I replaced the problematic line with
'disabled' => Holiday::whereDate('day', $now)->count()
Which does the job but I think is terrible for performance due to so many (in my opinion unecessary) round trips to the database.
The question
Could anyone shed some light on this?
Although I've found a solution, I don't think it would scale and I also didn't learn a thing from the experience, I still have no idea why the first query is only matching the first day and no other days. Or why the second one using where() doesn't match any days at all when it is comparing strings and I am using the exact format the strings are stored in on the database.
Or maybe the problem is not on the query, but on the Carbon object?
If you want to reproduce it, follow steps on this gist:
https://gist.github.com/alvarezrrj/50cd3669914f52ce8a6188771fdeafcd
DB::table('holidays') instantiates an Illuminate\Database\Query\Builder object. The where method modifies that object in place.
So if you're looping from January 1st-3rd and are adding a new where condition on each loop, that's going to fail because now you are basically querying this. Obviously the day column cannot match 3 different dates.
SELECT * FROM holidays
WHERE DATE(day) = '2022-01-01'
AND DATE(day) = '2022-01-02'
AND DATE(day) = '2022-01-03'
That's also why it only worked on the first loop for you, because at that point there is only 1 where condition.
You would need to move the instantiation inside the while loop so that it gets reset on each loop. Which is basically what you did in your solution.
Re: performance, what you were trying to do would not have saved you any DB cycles anyway. Each time you call count() you are hitting the database, regardless of whether it's a new $holidays object or not.
If you're concerned about performance, one thing you could do is fetch all of the holidays between the start & end date in a single query.
// May need to call toDateString() on $now and $last
$holidays = Holiday::whereBetween('day', [$now, $last])
->get()
->pluck('id', 'day'); // Assuming day is a DATE column not DATETIME or TIMESTAMP
// This will give you a collection with an underlying array like this:
// ['2022-07-04' => 1, '2022-12-25' => 2]
while($now->lte($last))
{
array_push($days, [
// Now you can instantly look it up in the array by the date
'disabled' => isset($holidays[$now->toDateString()]),
]);
$now->addDay();
}
Basically i have rate limit from my API provider for 50 simultaneous jobs. as an example lets say i have to run 500 jobs:
$jobs = $this->db->get('jobs')->result_array(); (loads all 500 jobs)
Bellow code loops api query for all 500 jobs.
foreach($jobs as $job)
{
//API call
}
each job has status parameter $job['status'], i want to do the following:
to avoid abuse of rate limit, i want to count rows with busy status
$busy = $this->db->get_where('jobs', ['status' => 'busy']->num_rows();
and keep on checking (looping) until $busy < 50
Final results
foreach($jobs as $job)
{
//API call
//Count busy jobs
//If busy >= 50 wait(50) and check again - (I need help with this part, no idea how to do it)
//If busy < 50 continue foreach loop
}
Hope all details are in place.
p.s.
I am using:
CodeIgniter3, PHP7.4
Edit:
To avoid confusion as mentioned in final result (//If busy >= 50 wait(50) and check again - (I need help with this part, no idea how to do it)
)
I am looking for a way to loop $busy SQL query until num_row() will be less than 50.
Found solution
$busy = $this->db->get_where('jobs', ['status' => 'busy']->num_rows();
while($busy >= 50 ) {
$busy = $this->db->get_where('jobs', ['status' => 'busy']->num_rows();
}
I have an orders table with orderStatus and paymentStatus fields. When an order is made, the orderStatus is set to initialized and paymentStatus set to pending.
At the point when the order is created, I want to check if paymentStatus changed. If it did not, change after 12 minutes I want to update orderStatus to completed and 'paymentStatustoaborted`.
I have a schedule task that checks every one minute but unfortunately I have not been able to run cron jobs on Bluehost. So I tried using a for loop in the create method of OrderObserver but the code doesn't work.
public function created(Order $order)
{
// check if user reservation record exist
$reservation = Reservation::where([
['user_id', $order->user_id],
['product_id', $order->product_id]
]);
if ($reservation) {
// delete reservation record
$reservation->delete();
}
// start 12 mins count down for payment
$period = ($order->created_at)->diffInMinutes();
for ($counter = 0; $period >= 12; ++$counter) {
$order->update([
'orderStatus' => 'completed',
'paymentStatus' => 'aborted'
]);
}
}
From php artisan tinker, I can see that this part of the code works
for ($counter = 0; $period >= 12; ++$counter) {
$order->update([
'orderStatus' => 'completed',
'paymentStatus' => 'aborted'
]);
}
Why does the code not run in the observable?
This might have to do something with the fact that you are blocking php executing for 12 minutes, by making it be stuck in the same for loop. You're probably exceeding the max executing time.
max_execution_time integer
This sets the maximum time in seconds a script is allowed to run before it is terminated by the parser. This helps prevent poorly written scripts from tying up the server. The default setting is 30. When running PHP from the command line the default setting is 0.
Seeing as artisan tinker runs from the command line, it makes sense that it works there.
EDIT:
I want to thanks #jimmix for giving me some idea to get started on my last post, But unfortunately, my post was put on hold. Due to the lack of details.
But here are the real scenario, I'm sorry if I didn't explain well my question.
From my CSV file, I have a raw data, then I will upload using my upload() function in into my phpmyadmin database with the table name "tbldumpbio",
See the table structure below:(tbldumpbio)
From my table tbldumpbio data, I have a function called processTimesheet()
Here's the code:
public function processTimesheet(){
$this->load->model('dbquery');
$query = $this->db->query("SELECT * FROM tbldumpbio");
foreach ($query->result() as $row){
$dateTimeExplArr = explode(' ', $row->datetimex);
$dateStr = $dateTimeExplArr[0];
$timeStr = $dateTimeExplArr[1];
if($row->status='C/Out' and !isset($timeStr) || empty($timeStr) ){
$timeStrOut ='';
} else {
$timeStrOut = $dateTimeExplArr[1];
}
if($row->status='C/In' and !isset($timeStr) || empty($timeStr) ){
$timeStrIn ='';
} else {
$timeStrIn = $dateTimeExplArr[1];
}
$data = array(
'ID' => '',
'companyAccessID' => '',
'name' => $row->name,
'empCompID' => $row->empid,
'date' => $dateStr,
'timeIn' => $timeStrIn,
'timeOut' => $timeStrOut,
'status' => '',
'inputType' => ''
);
$this->dbquery->modInsertval('tblempbioupload',$data);
}
}
This function will add another data into another table called "tblempbioupload". But here are the results that I'm getting with:
Please see the below data:(tblempbioupload)
The problem is:
the date should not be duplicated
Time In data should be added if the status is 'C/In'
Time Out data should be added if the status is 'C/Out'
The expected result should be something like this:
The first problem I see is that you have a time expressed as 15:xx:yy PM, which is an ambiguous format, as one can write 15:xx:yy AM and that would not be a valid time.
That said, if what you want is that every time the date changes a row should be written, you should do just that: store the previous date in a variable, then when you move to the next record in the source table, you compare the date with the previous one and if they differ, then you insert the row, otherwise you simply progress reading the next bit of data.
Remember that this approach works only if you're certain that the input rows are in exact order, which means ordered by EmpCompId first and then by date and then by time; if they aren't this procedure doesn't work properly.
I would probably try another approach: if (but this is not clear from your question) only one row per empcompid and date should be present, i would do a grouping query on the source table, finding the minimum entrance time, another one to find the maximum exit date, and use both of them as a source for the insert query.
I've currently got a task that collects usage statistics from my website and is set to automatically email them to the client.
The problem is that the 1st of the month may be a non-work day which I guess is not a disaster but looks a bit unprofessional.
This is how I've scheduled it currently:
$schedule
->command("report", [ "--email" => "example#example.com" ]) //My command which accepts the email as a parameter
->monthly();
I was thinking of doing the following:
$schedule
->command("report", [ "--email" => "example#example.com" ]) //My command which accepts the email as a parameter
->monthlyOn(1)
->when(function () {
if (in_array(Carbon::now()->dayOfWeek,[Carbon::SATURDAY,Carbon::SUNDAY])) {
return false;
}
return true;
});
$schedule
->command("report", [ "--email" => "example#example.com" ]) //My command which accepts the email as a parameter
->monthlyOn(2)
->when(function () {
if (Carbon::now()->dayOfWeek == Carbon::MONDAY) {
return true; //1st was in the weekend
}
return false;
});
$schedule
->command("report", [ "--email" => "example#example.com" ]) //My command which accepts the email as a parameter
->monthlyOn(3)
->when(function () {
if (Carbon::now()->dayOfWeek == Carbon::MONDAY) {
return true; //1st and 2nd was in the weekend
}
return false;
});
However this looks like a very strange thing to do for something as simple as that.
So my questions:
If a when condition fails, does the task get attempted again until it succeeds? (Assuming this is a no but not sure)
Is there a simpler way to run a task on the first work-day of the month?
I'll post this as a community wiki answer for others to utilise if they ever need to in the future:
You can put the condition into the actual crontab command:
[ "$(date '+%a')" = "Mon" ] && echo "It's Monday"
Now, if this
condition is true on one of the first seven days in a month, you have
its first Monday. Note that in the crontab, the percent-syntax needs
to be escaped though:
0 12 1-7 * * [ "$(date '+\%a')" = "Mon" ] && echo "It's Monday"
the above is referenced from: https://superuser.com/questions/428807/run-a-cron-job-on-the-first-monday-of-every-month
This way it's set to a cronjob which will only run once per month on the Monday. I believe this will be your most effective method to achieve what you're trying to do.
I know the accepted answer is correct but, there are always different ways I prefer this:
->monthlyOn(Carbon::parse('first monday of this month')->format('d'), '5:00')