Search code examples
phplaravelcronscheduled-tasks

Make Laravel Schedule-Tasks configable


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();
    });
}

Examples: enter image description here

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.


Solution

  • 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);