PHP code example of zuqongtech / laravel-kronos

1. Go to this page and download the library: Download zuqongtech/laravel-kronos library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

zuqongtech / laravel-kronos example snippets




namespace App\Providers;

use App\Jobs\GenerateMemberStatementsJob;
use App\Jobs\NotifyStakeholdersJob;
use App\Jobs\ValidateContributionsJob;
use App\Models\ScheduledTask;
use Illuminate\Support\ServiceProvider;
use ZuqongTech\Kronos\Facades\Kronos;

class KronosServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // ── Rule: when a ScheduledTask is enabled, write it to kronos.yaml ──
        Kronos::rule('activate_scheduled_task')
            ->when(ScheduledTask::class, fn ($task) => $task->is_enabled)
            ->onEvents(['created', 'updated'])
            ->produces(fn ($task) => [
                'id'                  => $task->id,
                'command'             => $task->command,
                'cron_expression'     => $task->cron_expression,
                'timezone'            => $task->timezone ?? 'UTC',
                'without_overlapping' => true,
                'on_one_server'       => true,
                'enabled'             => true,
            ]);

        // ── Workflow: multi-step monthly payroll ───────────────────────────
        Kronos::workflow('monthly_payroll')
            ->trigger()->cron('0 0 1 * *')->timezone('Pacific/Port_Moresby')

            ->step('validate_contributions')
                ->run(ValidateContributionsJob::class)
                ->retries(3, delaySeconds: 120)
                ->timeout(300)

            ->step('generate_statements')
                ->run(GenerateMemberStatementsJob::class)
                ->after('validate_contributions')
                ->timeout(600)

            ->step('notify_stakeholders')
                ->run(NotifyStakeholdersJob::class)
                ->after('generate_statements')
                ->retries(2)

            ->onFailure(fn () => \Log::critical('Monthly payroll workflow failed'))
            ->register();
    }
}



namespace App\Jobs;

use ZuqongTech\Kronos\Contracts\KronosStep;
use ZuqongTech\Kronos\DAG\WorkflowContext;

class ValidateContributionsJob implements KronosStep
{
    public function handle(WorkflowContext $context): array
    {
        $result = ContributionValidator::run();

        // Write to shared context — available to all downstream steps
        $context->set('validated_count', $result->count);
        $context->set('has_errors', $result->hasErrors());

        return ['count' => $result->count];
    }
}

$runId = Kronos::trigger('monthly_payroll', ['initiated_by' => 'admin']);

Kronos::rule('rule_name')
    ->when(MyModel::class, fn ($model) => $model->is_active)
    ->onEvents(['created', 'updated'])   // defaults to all three
    ->produces(fn ($model) => [
        'command'         => "my-command:{$model->id}",
        'cron_expression' => $model->cron,
        'timezone'        => $model->timezone,
        'enabled'         => true,
    ]);

Kronos::rule('payroll_auto_schedule')
    ->when(PayrollConfig::class, fn ($c) => $c->auto_schedule === true)
    ->andWhen(CompanySettings::class, fn ($s) => $s->subscription_active === true)
    ->produces(fn ($config) => [
        'command'         => 'payroll:process',
        'cron_expression' => $config->cron_expression,
    ]);

Kronos::workflow('data_ingestion')
    ->trigger()->onEvent(\App\Events\DataFileUploaded::class)

    ->step('validate_file')
        ->run(\App\Jobs\ValidateUploadedFileJob::class)
        ->retries(2)
        ->timeout(120)

    ->step('parse_records')
        ->run(\App\Jobs\ParseRecordsJob::class)
        ->after('validate_file')
        ->timeout(300)

    ->parallel(
        step('notify_ops')->run(\App\Jobs\NotifyOpsJob::class),
        step('update_dashboard')->run(\App\Jobs\UpdateDashboardJob::class),
    )
    ->after('parse_records')   // both parallel steps depend on parse_records

    ->step('finalize')
        ->run(\App\Jobs\FinalizeIngestionJob::class)
        ->after('notify_ops', 'update_dashboard')

    ->onSuccess(fn () => \Log::info('Data ingestion complete'))
    ->onFailure(fn () => \Slack::send('#ops', 'Data ingestion failed'))
    ->register();

class ParseRecordsJob implements KronosStep
{
    public function handle(WorkflowContext $context): array
    {
        // Read data written by the previous step
        $filePath = $context->get('validated_file_path');

        $records = RecordParser::parse($filePath);

        // Write for downstream steps
        $context->set('record_count', count($records));
        $context->set('parse_errors', $records->errors());

        return ['parsed' => count($records)];
    }
}

$context->get('key', $default);    // Read a value
$context->set('key', $value);      // Write and persist immediately
$context->has('key');              // Check existence
$context->forget('key');           // Remove a key
$context->merge(['a' => 1, ...]);  // Bulk write
$context->all();                   // Get all data

->trigger()
    ->cron('0 0 1 * *')
    ->timezone('Pacific/Port_Moresby')

Kronos::workflow('contribution_processing')
    ->trigger()->cron('0 2 * * *')

    ->step('check_threshold')
        ->run(CheckContributionThresholdJob::class)

    ->branch()
        ->when(fn ($ctx) => $ctx->get('threshold_met') === true)
            ->step('full_processing')->run(FullProcessingJob::class)->endArm()
        ->otherwise()
            ->step('partial_processing')->run(PartialProcessingJob::class)->endArm()
    ->endBranch()

    ->step('finalise')
        ->run(FinaliseJob::class)
    ->register();



namespace App\Jobs;

use ZuqongTech\Kronos\Contracts\KronosStep;
use ZuqongTech\Kronos\DAG\WorkflowContext;

class SendPayrollNotificationsJob implements KronosStep
{
    /**
     * Execute this step.
     *
     * @return array|null  Return an array to store as step output, or null.
     */
    public function handle(WorkflowContext $context): array|null
    {
        $count = $context->get('record_count', 0);

        Notification::send(
            User::role('payroll-admin')->get(),
            new PayrollCompletedNotification($count)
        );

        return ['notified_count' => User::role('payroll-admin')->count()];
    }
}

->step('my_step')
    ->run(MyStepJob::class, ['param' => 'value'])  // constructor params
    ->after('upstream_step')                        // dependency
    ->retries(3, delaySeconds: 60)                  // retry 3x, 60s backoff
    ->timeout(300)                                  // 5 minute timeout
    ->parallel()                                    // hint: can run in parallel
    ->skipUnless('context_key')                     // skip if context key is falsy
    ->onSuccess(fn () => Log::info('...'))
    ->onFailure(fn () => Slack::send('#alerts', '...'))

use ZuqongTech\Kronos\Models\KronosScheduledTask;

KronosScheduledTask::create([
    'name'                => 'clear_expired_sessions',
    'command'             => 'session:gc',
    'cron_expression'     => '0 3 * * *',
    'timezone'            => 'UTC',
    'enabled'             => true,
    'without_overlapping' => true,
    'on_one_server'       => true,
    'run_in_background'   => true,
]);

Kronos::rule('enable_report_task')
    ->when(ReportSchedule::class, fn ($r) => $r->active && $r->cron !== null)
    ->produces(fn ($r) => [
        'id'              => $r->id,
        'command'         => "reports:generate --id={$r->id}",
        'cron_expression' => $r->cron,
        'timezone'        => $r->timezone,
        'enabled'         => true,
    ]);

// app/Providers/Filament/AdminPanelProvider.php

use ZuqongTech\Kronos\Filament\KronosPlugin;

->plugins([
    KronosPlugin::make(),
])

use ZuqongTech\Kronos\Events\WorkflowCompleted;
use ZuqongTech\Kronos\Events\WorkflowFailed;
use ZuqongTech\Kronos\Events\WorkflowStepCompleted;
use ZuqongTech\Kronos\Events\WorkflowStepFailed;

protected $listen = [
    WorkflowCompleted::class     => [SendWorkflowCompletionSlack::class],
    WorkflowFailed::class        => [AlertOpsTeam::class],
    WorkflowStepCompleted::class => [UpdateProgressDashboard::class],
    WorkflowStepFailed::class    => [LogStepFailure::class],
];

// WorkflowCompleted / WorkflowFailed
$event->run;          // KronosWorkflowRun model

// WorkflowFailed
$event->reason;       // string error message

// WorkflowStepCompleted / WorkflowStepFailed
$event->run;          // KronosWorkflowRun model
$event->stepName;     // string

// WorkflowStepFailed
$event->error;        // string exception message

// config/kronos.php

return [

    // Path to the canonical YAML config file
    'config_path' => storage_path('kronos.yaml'),

    // Enable multi-node / distributed mode
    // Sets onOneServer() on all entries and uses Redis as primary config store
    'multi_node' => env('KRONOS_MULTI_NODE', false),

    // Redis connection name (from config/database.php)
    'redis_connection' => env('KRONOS_REDIS_CONNECTION', 'default'),

    // Queue connection and name for Kronos internal jobs
    'queue' => [
        'connection' => env('KRONOS_QUEUE_CONNECTION', 'redis'),
        'name'       => env('KRONOS_QUEUE_NAME', 'kronos'),
    ],

    // Inbound webhook trigger endpoint
    'webhook' => [
        'enabled' => env('KRONOS_WEBHOOK_ENABLED', false),
        'secret'  => env('KRONOS_WEBHOOK_SECRET'),
        'prefix'  => env('KRONOS_WEBHOOK_PREFIX', 'kronos'),
    ],

    // Run history retention in days (null = keep forever)
    'retention_days' => env('KRONOS_RETENTION_DAYS', 30),

    // Filament UI plugin settings
    'filament' => [
        'enabled'   => env('KRONOS_FILAMENT_ENABLED', true),
        'panel_id'  => env('KRONOS_FILAMENT_PANEL', 'admin'),
        'nav_group' => 'Kronos',
        'nav_sort'  => 90,
    ],

    // Default timezone for all schedules
    'timezone' => env('KRONOS_TIMEZONE', 'UTC'),
];

use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Event;
use ZuqongTech\Kronos\Events\WorkflowCompleted;
use ZuqongTech\Kronos\Jobs\ExecuteWorkflowStep;

it('triggers the monthly payroll workflow', function () {
    Bus::fake();
    Event::fake();

    $runId = Kronos::trigger('monthly_payroll');

    Bus::assertDispatched(ExecuteWorkflowStep::class, fn ($job) =>
        $job->stepName === 'validate_contributions'
    );
});

it('fires WorkflowCompleted on success', function () {
    Event::fake();

    // Simulate a completed run
    $run = KronosWorkflowRun::factory()->completed()->create();
    event(new WorkflowCompleted($run));

    Event::assertDispatched(WorkflowCompleted::class);
});

it('writes validated_count to context', function () {
    $run = KronosWorkflowRun::factory()->create(['context' => []]);
    $context = new WorkflowContext($run);

    $job = new ValidateContributionsJob();
    $output = $job->handle($context);

    expect($output)->toHaveKey('count')
        ->and($context->get('validated_count'))->toBeInt();
});
bash
php artisan kronos:install
bash
php artisan vendor:publish --tag=kronos-config
php artisan vendor:publish --tag=kronos-migrations
php artisan migrate
bash
php artisan kronos:trigger monthly_payroll