My idea was to make the Laravel Schedule-Taks configurable, meaning that every user has the possibility to set up the tasks the way he wants.
For this I have created a new database table, which contains all relevant information for the execution of the schedule tasks:
public function up()
{
Schema::create('cronjobs', function (Blueprint $table) {
$table->id();
$table->string('name')->unique(); //the command
$table->string('class'); //the command class name
$table->string('description'); //a description for the user
$table->string('interval'); //the interval e.g. "everyFifteenMinutes" or "weeklyOn"
$table->string('intervalOptions'); //sometimes some intervals has parameters as {1, '7:30'}
$table->boolean('isActive'); //is the cronjob/command aktive?
$table->boolean('showInFrontend'); //can user see the cronjob?
$table->timestamps();
});
}
In the App\Console\Kernel.php I have now thought of the following logic:
protected function schedule(Schedule $schedule)
{
$allCronjobs = Cronjob::all();
foreach ($allCronjobs as $cronjob) {
//some generic informations over the cronjob itself
$data = [
'command' => $cronjob->name,
'description' => $cronjob->description,
'isActive' => $cronjob->isActive,
'interval' => $cronjob->interval,
'intervalOptions' => $cronjob->intervalOptions,
];
try {
if ($cronjob->isActive == true) {
echo "[" . Carbon::now()->toDateTimeString() . "]: try to run: command('{$cronjob->name}')->{$cronjob->interval}({$cronjob->intervalOptions});\n";
$CronCommand = $schedule->command("{$cronjob->name}");
//here comes the "magic" here we build the command with there options
call_user_func_array([$CronCommand, $cronjob->interval], explode(", ", $cronjob->intervalOptions))
->onSuccess(function (Stringable $output) use ($data) {
$data['last_execution'] = Carbon::now();
$data['log'] = "successfully executed:\n" . $output;
\Log::info("cronjob executed successfully",$data);
})
->onFailure(function (Stringable $output) use ($data) {
$data['last_execution'] = Carbon::now();
$data['log'] = "command failed: " . $output;
\Log::warning("cronjob could not be executed",$data);
});
}
} catch (\Exception $exception) {
$data['log'] = "Error: " . $exception->getMessage();
\Log::error("cronjob could not be executed",$data);
}
}
}
In the past I had always run the cronjobs/taks like this
$schedule->command("{$cronjob->name}")->{$cronjob->interval}($cronjob->intervalOptions);
This had also "worked" so far, but the interval options were completely ignored. So it happened for example that a cronjob, which should start every day at 4am always ran at midnight (parameters were ignored and apparently the default values were taken).
My problem is that I would like to be able to change the cronjobs at any time from "outside". So that the user always has the chance to set the inverval himself.
Say {command}->{interval}( {intervallOptions} );
Can someone help me here to implement the whole thing dynamically so that it also works with the interval options?
Thanks.
As i see it, this snippet would never work how your data is structured. $cronjob->intervalOptions
is a string, this won't work as there only will be parsed 1 parameter, this sandbox i made to illustrate it.
$schedule->command("{$cronjob->name}")->{$cronjob->interval}($cronjob->intervalOptions);
Instead if you change your intervalOptions to be an JSON object. In your Cronjob class cast it automatically and save intervalOptions like so [1, '02:00']
. So in Cronjob.php
cast it to JSON.
protected $casts = [
'intervalOptions' => 'array',
];
Now you can unwrap your array as multiple parameters with the operator ...
.
$schedule->command("{$cronjob->name}")->{$cronjob->interval}(... $cronjob->intervalOptions);