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:
composer require obelaw/runner-laravelRun the migrations to create the necessary database tables:
php artisan migrateConfiguration
By default, runners are stored in the base_path('runners') directory. You can add additional runner paths using the RunnerPool:
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:
php artisan runner:makeThis 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
// 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:
protected string $type = Runner::TYPE_ONCE;TYPE_ALWAYS
Runs every time the runner command is executed:
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
protected ?string $schedule = '0 0 * * *'; // Daily at midnightUsing Helper Methods
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:
$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 datesSetting Up Cron Job
To automatically execute scheduled runners, add this entry to your server's crontab:
* * * * * cd /path-to-project && php artisan runner:run --scheduled >> /dev/null 2>&1This 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:
crontab -eRunning Runners
Run All Runners
php artisan runner:runRun by Tag
php artisan runner:run --tag=setupRun Specific Runner
php artisan runner:run 2024_11_01_120000_create_categories.phpForce Re-execution
Re-run all runners, including those already executed (TYPE_ONCE):
php artisan runner:run --forceRun Scheduled Runners Only
Execute only runners with defined schedules:
php artisan runner:run --scheduledCombined Options
php artisan runner:run --tag=maintenance --scheduled --forceListing Runners
Display all available runners:
php artisan runner:listFilter by Tag
php artisan runner:list --tag=setupFilter by Type
php artisan runner:list --type=once
php artisan runner:list --type=alwaysFilter by Status
php artisan runner:list --status=executed
php artisan runner:list --status=pendingProgrammatic Usage
Use the RunnerService class for programmatic execution:
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:
[
'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:
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:
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 millisecondsDatabase Schema
runners Table
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
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:
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:
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:
// 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:
// 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
- Use Timestamps: Always prefix runner files with timestamps (format:
YYYY_MM_DD_HHMMSS_) - Descriptive Names: Use clear, descriptive names for runners
- Set Descriptions: Always add descriptions to explain what the runner does
- Use Tags: Organize runners with consistent tagging
- Set Priorities: Use priorities when execution order matters
- Error Handling: Wrap critical operations in try-catch blocks
- Logging: Use the
info()method or Laravel's Log facade for output - Schedule Wisely: Use appropriate schedules to avoid resource contention
- Test Locally: Test runners in development before production deployment
- Document Dependencies: Note any dependencies or prerequisites in descriptions
Example Use Cases
One-Time Data Migration
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
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
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
--forceflag to bypass execution history
Schedule Not Working
- Verify cron expression is valid
- Check if
shouldRunBySchedule()returns true - Ensure the schedule period has elapsed
- Use
--scheduledflag to test scheduled runners
Database Tracking Issues
- Run migrations:
php artisan migrate - Check database connection
- Verify
trackExecutionsis not disabled
License
This package is open-sourced software licensed under the MIT license.