Skip to content

Runner Laravel ​

A powerful Laravel package for managing and executing one-time or recurring tasks with scheduling capabilities, execution tracking, and comprehensive logging.

Overview ​

Runner Laravel provides a flexible system for creating and managing executable tasks (runners) in your Laravel application. It's perfect for data migrations, seeding operations, maintenance tasks, and any code that needs to run once or on a schedule.

Features ​

  • Flexible Execution Types: Run tasks once or repeatedly
  • Cron Scheduling: Built-in scheduling with cron expressions
  • Tag-Based Filtering: Organize and filter runners by tags
  • Priority System: Control execution order with priorities
  • Execution Tracking: Automatic database tracking of runs
  • Comprehensive Logging: Detailed logs with execution time and output
  • Force Mode: Re-execute runners regardless of history
  • Artisan Commands: Full CLI support for creating, listing, and running tasks

Installation ​

Install the package via Composer:

bash
composer require obelaw/runner-laravel

Run the migrations to create the necessary database tables:

bash
php artisan migrate

Configuration ​

By default, runners are stored in the base_path('runners') directory. You can add additional runner paths using the RunnerPool:

php
use Obelaw\Runner\RunnerPool;

// In a service provider
RunnerPool::addPath(app_path('Runners'));
RunnerPool::addPath(database_path('runners'));

Creating Runners ​

Using the Artisan Command ​

The easiest way to create a runner is using the runner:make command:

bash
php artisan runner:make

This interactive command will prompt you for:

  • Runner name
  • Tag (optional)
  • Description (optional)
  • Type (once/always)
  • Priority (optional)
  • Schedule (optional, cron expression)

Manual Creation ​

Create a new PHP file in your runners directory with a timestamp prefix:

php
<?php
// File: runners/2024_11_01_120000_create_categories.php

use Obelaw\Runner\Runner;

return new class extends Runner
{
    /**
     * The tag of the runner for filtering execution.
     */
    public ?string $tag = 'setup';

    /**
     * The priority of the runner (lower numbers run first).
     */
    public int $priority = 0;

    /**
     * The description of what this runner does.
     */
    public ?string $description = 'Create default product categories';

    /**
     * The type of runner execution: 'once' or 'always'.
     */
    protected string $type = Runner::TYPE_ONCE;

    /**
     * Execute the runner logic.
     */
    public function handle(): void
    {
        // Your implementation here
        $this->info('Creating categories...');
    }

    /**
     * Hook that runs before the main handle method.
     */
    public function before(): void
    {
        // Optional: Add pre-execution logic
    }

    /**
     * Hook that runs after the main handle method.
     */
    public function after(): void
    {
        // Optional: Add post-execution logic
    }

    private function info(string $message): void
    {
        echo "[INFO] " . $message . PHP_EOL;
    }
};

Runner Types ​

TYPE_ONCE (Default) ​

Runs only once. After execution, it won't run again unless forced:

php
protected string $type = Runner::TYPE_ONCE;

TYPE_ALWAYS ​

Runs every time the runner command is executed:

php
protected string $type = Runner::TYPE_ALWAYS;

Scheduling ​

Runners support cron-based scheduling using the Schedulable trait. You can set schedules directly or use helper methods:

Direct Cron Expression ​

php
protected ?string $schedule = '0 0 * * *'; // Daily at midnight

Using Helper Methods ​

php
public function __construct()
{
    $this->everyMinute();      // Every minute: * * * * *
    $this->everyMinutes(5);     // Every 5 minutes: */5 * * * *
    $this->hourly();            // Hourly: 0 * * * *
    $this->everyHours(3);       // Every 3 hours: 0 */3 * * *
    $this->daily();             // Daily at midnight: 0 0 * * *
    $this->dailyAt('14:30');    // Daily at 2:30 PM: 30 14 * * *
    $this->weekly();            // Weekly on Sunday: 0 0 * * 0
    $this->weeklyOn(1);         // Weekly on Monday: 0 0 * * 1
    $this->monthly();           // Monthly on 1st: 0 0 1 * *
    $this->monthlyOn(15);       // Monthly on 15th: 0 0 15 * *
    $this->cron('0 8-23,0-3 * * *'); // Custom cron
}

Schedule Information ​

Get schedule information:

php
$runner->getSchedule();           // Get cron expression
$runner->isValidSchedule();       // Validate cron expression
$runner->getNextRunTime();        // Get next run time
$runner->getPreviousRunTime();    // Get previous run time
$runner->getNextRunDates(5);      // Get next 5 run dates

Setting Up Cron Job ​

To automatically execute scheduled runners, add this entry to your server's crontab:

bash
* * * * * cd /path-to-project && php artisan runner:run --scheduled >> /dev/null 2>&1

This will run every minute and execute any runners that are due according to their schedule. Replace /path-to-project with the absolute path to your Laravel project.

To edit your crontab:

bash
crontab -e

Running Runners ​

Run All Runners ​

bash
php artisan runner:run

Run by Tag ​

bash
php artisan runner:run --tag=setup

Run Specific Runner ​

bash
php artisan runner:run 2024_11_01_120000_create_categories.php

Force Re-execution ​

Re-run all runners, including those already executed (TYPE_ONCE):

bash
php artisan runner:run --force

Run Scheduled Runners Only ​

Execute only runners with defined schedules:

bash
php artisan runner:run --scheduled

Combined Options ​

bash
php artisan runner:run --tag=maintenance --scheduled --force

Listing Runners ​

Display all available runners:

bash
php artisan runner:list

Filter by Tag ​

bash
php artisan runner:list --tag=setup

Filter by Type ​

bash
php artisan runner:list --type=once
php artisan runner:list --type=always

Filter by Status ​

bash
php artisan runner:list --status=executed
php artisan runner:list --status=pending

Programmatic Usage ​

Use the RunnerService class for programmatic execution:

php
use Obelaw\Runner\Services\RunnerService;
use Obelaw\Runner\RunnerPool;

$service = new RunnerService(RunnerPool::getPaths());

// Run all runners
$summary = $service->run();

// Run with tag filter
$summary = $service->run('setup');

// Force execution
$service->force(true)->run();

// Run only scheduled runners
$service->scheduledOnly(true)->run();

// Run specific runner
$summary = $service->runByName('2024_11_01_120000_create_categories.php');

// Disable execution tracking
$service->trackExecutions(false)->run();

Execution Summary ​

The service returns an execution summary:

php
[
    'executed_count' => 5,
    'skipped_count' => 2,
    'error_count' => 0,
    'executed_files' => ['file1.php', 'file2.php', ...],
    'skipped_files' => ['file3.php', ...],
    'errors' => [],
    'success' => true
]

Execution Tracking ​

Database Models ​

RunnerModel ​

Tracks runner executions:

php
use Obelaw\Runner\Models\RunnerModel;

// Check if executed
RunnerModel::hasBeenExecuted('runner_name.php');

// Mark as executed
RunnerModel::markAsExecuted('runner_name.php', [
    'tag' => 'setup',
    'type' => 'once',
    'description' => 'Create categories'
]);

// Get by tag
RunnerModel::getByTag('setup');

// Get by type
RunnerModel::getOnceRunners();
RunnerModel::getAlwaysRunners();

// Get logs
$runner = RunnerModel::where('name', 'runner.php')->first();
$runner->lastLog();
$runner->failedLogs();

RunnerLog ​

Detailed execution logs:

php
use Obelaw\Runner\Models\RunnerLog;

// Get recent logs
RunnerLog::getRecentLogs('runner_name.php', 10);

// Get failed logs
RunnerLog::getFailedLogs(50);

// Query by status
RunnerLog::status('completed')->get();
RunnerLog::failed()->get();
RunnerLog::completed()->get();

// Query by runner
RunnerLog::forRunner('runner_name.php')->get();

// Query by tag
RunnerLog::withTag('setup')->get();

// Get execution time
$log = RunnerLog::find(1);
$log->getExecutionTimeInSeconds(); // In seconds
$log->execution_time; // In milliseconds

Database Schema ​

runners Table ​

php
Schema::create('runners', function (Blueprint $table) {
    $table->id();
    $table->string('name')->unique();
    $table->string('tag')->nullable()->index();
    $table->text('description')->nullable();
    $table->integer('priority')->default(0);
    $table->enum('type', ['once', 'always'])->default('once');
    $table->timestamp('executed_at');
    $table->timestamps();
});

runner_logs Table ​

php
Schema::create('runner_logs', function (Blueprint $table) {
    $table->id();
    $table->string('runner_name')->index();
    $table->string('tag')->nullable()->index();
    $table->enum('type', ['once', 'always'])->default('once');
    $table->enum('status', ['started', 'completed', 'failed'])->index();
    $table->text('output')->nullable();
    $table->text('error')->nullable();
    $table->integer('execution_time')->nullable()->comment('In milliseconds');
    $table->timestamp('started_at')->nullable();
    $table->timestamp('completed_at')->nullable();
    $table->timestamps();
});

Advanced Features ​

Conditional Execution ​

Override the shouldRun() method:

php
public function shouldRun(): bool
{
    // Only run in production
    if (!app()->environment('production')) {
        return false;
    }
    
    // Only run if condition is met
    if (!SomeModel::exists()) {
        return false;
    }
    
    return parent::shouldRun();
}

Custom Execution Logic ​

Use hooks for complex workflows:

php
public function before(): void
{
    Log::info('Starting runner execution');
    DB::beginTransaction();
}

public function handle(): void
{
    // Main logic
}

public function after(): void
{
    DB::commit();
    Log::info('Runner completed successfully');
}

Priority-Based Execution ​

Lower priority numbers run first:

php
// This runs first
public int $priority = 0;

// This runs second
public int $priority = 10;

// This runs last
public int $priority = 100;

Tagging Strategy ​

Organize runners with tags:

php
// Setup runners
public ?string $tag = 'setup';

// Maintenance runners
public ?string $tag = 'maintenance';

// Data migration runners
public ?string $tag = 'migration';

// Installation runners
public ?string $tag = 'install';

Best Practices ​

  1. Use Timestamps: Always prefix runner files with timestamps (format: YYYY_MM_DD_HHMMSS_)
  2. Descriptive Names: Use clear, descriptive names for runners
  3. Set Descriptions: Always add descriptions to explain what the runner does
  4. Use Tags: Organize runners with consistent tagging
  5. Set Priorities: Use priorities when execution order matters
  6. Error Handling: Wrap critical operations in try-catch blocks
  7. Logging: Use the info() method or Laravel's Log facade for output
  8. Schedule Wisely: Use appropriate schedules to avoid resource contention
  9. Test Locally: Test runners in development before production deployment
  10. Document Dependencies: Note any dependencies or prerequisites in descriptions

Example Use Cases ​

One-Time Data Migration ​

php
public ?string $tag = 'migration';
public ?string $description = 'Migrate legacy user data to new schema';
protected string $type = Runner::TYPE_ONCE;

public function handle(): void
{
    User::chunk(100, function ($users) {
        foreach ($users as $user) {
            // Migration logic
        }
    });
}

Recurring Cleanup Task ​

php
public ?string $tag = 'maintenance';
public ?string $description = 'Clean up expired sessions daily';
protected string $type = Runner::TYPE_ALWAYS;
protected ?string $schedule = '0 2 * * *'; // 2 AM daily

public function handle(): void
{
    Session::where('last_activity', '<', now()->subDays(30))->delete();
}

Initial Setup Runner ​

php
public ?string $tag = 'install';
public ?string $description = 'Create default application settings';
protected string $type = Runner::TYPE_ONCE;
public int $priority = 1;

public function handle(): void
{
    Setting::create(['key' => 'app_initialized', 'value' => true]);
}

Troubleshooting ​

Runners Not Executing ​

  • Check if the runner file exists in a configured path
  • Verify the file returns a runner instance
  • Check execution history (TYPE_ONCE runners won't re-run)
  • Use --force flag to bypass execution history

Schedule Not Working ​

  • Verify cron expression is valid
  • Check if shouldRunBySchedule() returns true
  • Ensure the schedule period has elapsed
  • Use --scheduled flag to test scheduled runners

Database Tracking Issues ​

  • Run migrations: php artisan migrate
  • Check database connection
  • Verify trackExecutions is not disabled

License ​

This package is open-sourced software licensed under the MIT license.

Repository ​

https://github.com/obelaw/runner-laravel

Released under the MIT License.