nabeghe/cronark

A lightweight, cron-based background job scheduler and worker manager for PHP.

Installs: 9

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/nabeghe/cronark

v0.2.2 2026-02-09 17:47 UTC

This package is auto-updated.

Last update: 2026-02-09 17:48:45 UTC


README

A lightweight, cron-based background job scheduler and worker manager for PHP.

Cronark provides a simple yet powerful solution for running background jobs in PHP applications. Unlike traditional queue systems that require external services (Redis, RabbitMQ, Beanstalkd), Cronark works with just cron and minimal storage; making it perfect for shared hosting, small to medium projects, or anywhere you want to avoid infrastructure complexity.

๐ŸŽฏ Why Cronark?

The Problem with Traditional Queue Systems

Most PHP queue solutions come with significant challenges:

  • โŒ Infrastructure Overhead: Require Redis, RabbitMQ, or other external services
  • โŒ Shared Hosting: Not available on most shared hosting environments
  • โŒ Complexity: Learning curve, configuration, and maintenance burden
  • โŒ Resource Heavy: Memory consumption and process management complexity
  • โŒ Overkill: Too much for simple background job needs

The Cronark Solution

Cronark takes a different approach:

  • โœ… Zero Dependencies: Just PHP 8.1+ and cron (available everywhere)
  • โœ… Shared Hosting Friendly: Works on any hosting with cron access
  • โœ… Simple Setup: Define jobs, register with cron, done
  • โœ… Lightweight: Minimal resource footprint
  • โœ… Process-Safe: Prevents duplicate worker execution automatically
  • โœ… Flexible Storage: File-based by default, easily customizable to database or Redis

๐Ÿ“ฆ Installation

Install via Composer:

composer require nabeghe/cronark

Requirements:

  • PHP 8.1 or higher
  • Cron access (available on virtually all hosting providers)

๐Ÿš€ Quick Start

1. Create a Job

<?php

use Nabeghe\Cronark\Cronark;

class SendEmailsJob
{
    public function __construct(private Cronark $cronark)
    {
    }

    public function __invoke()
    {
        // Your job logic here
        $this->cronark->print('Sending emails...');
        
        // Send pending emails
        // ...
        
        $this->cronark->print('Emails sent successfully!');
    }
}

2. Register the Job

Create a worker file (e.g., worker.php):

<?php

require 'vendor/autoload.php';

use Nabeghe\Cronark\Cronark;

$cronark = new Cronark();

// Register jobs for the 'email' worker
$cronark->addJob(SendEmailsJob::class, 'email');
$cronark->addJob(ProcessNewsletterJob::class, 'email');

// Start the worker
$cronark->start('email');

3. Setup Cron

Add to your crontab:

# Run email worker every minute
* * * * * php /path/to/worker.php

That's it! Your jobs will now run continuously in the background.

๐ŸŽจ Features

Multiple Workers Run different workers for different job types:

$cronark = new Cronark();

// Email worker - runs every minute
$cronark->addJob(SendEmailsJob::class, 'email');
$cronark->addJob(ProcessBouncesJob::class, 'email');

// Data processing worker - runs every 5 minutes
$cronark->addJob(ImportDataJob::class, 'data');
$cronark->addJob(GenerateReportsJob::class, 'data');

// Cleanup worker - runs hourly
$cronark->addJob(CleanupTempFilesJob::class, 'cleanup');
$cronark->addJob(PurgeOldLogsJob::class, 'cleanup');

// Start specific worker
$worker = $argv ?? 'email';[1]
$cronark->start($worker);

Process Management

Cronark automatically prevents duplicate workers:

// If worker is already running, this will abort
$cronark->start('email');

// Check if worker is active
if ($cronark->isActive('email', $pid)) {
    echo "Worker is running with PID: $pid";
}

// Gracefully stop a worker
$cronark->kill('email');

// Stop all workers
$cronark->killAll();

Job Ordering

Control job execution order:

// Add to end (default)
$cronark->addJob(JobA::class, 'worker');
$cronark->addJob(JobB::class, 'worker');

// Insert at specific position
$cronark->addJob(UrgentJob::class, 'worker', 0); // First

// Jobs execute: UrgentJob -> JobA -> JobB -> repeat

Custom Storage

Implement your own storage backend (Database, Redis, Memcached, etc.):

use Nabeghe\Cronark\StorageInterface;

class DatabaseStorage implements StorageInterface
{
    public function get(string $key, ?string $worker = null): mixed
    {
        // Fetch from database
        return DB::table('cronark_storage')
            ->where('worker', $worker)
            ->where('key', $key)
            ->value('value');
    }

    public function set(string $key, mixed $value, ?string $worker = null): bool
    {
        // Save to database
        return DB::table('cronark_storage')->updateOrInsert(
            ['worker' => $worker, 'key' => $key],
            ['value' => serialize($value)]
        );
    }
}

$cronark = new Cronark(new DatabaseStorage());

Built-in Storage:

  • File-based (default): Zero setup, works everywhere
  • Custom: Implement StorageInterface for database, Redis, etc.

Lifecycle Hooks

Customize worker behavior:

class CustomCronark extends Cronark
{
    protected function onStarted(string $worker): void
    {
        // Log worker start
        Log::info("Worker {$worker} started");
    }

    protected function onStopped(string $worker): void
    {
        // Log worker stop
        Log::info("Worker {$worker} stopped");
    }

    protected function onError(Throwable $e, string $worker): void
    {
        // Custom error handling
        Log::error("Worker {$worker} error: " . $e->getMessage());
        parent::onError($e, $worker);
    }

    protected function onJobCreating(): void
    {
        // Before each job instantiation
        DB::reconnect(); // Reconnect to database
    }
}

Performance Tuning

Control CPU usage by adding delay between job executions:

// No delay (default) - maximum speed
$cronark = new Cronark();
$cronark->start('worker');

// Balanced - 50ms delay (recommended for shared hosting)
$cronark = new Cronark();
$cronark->setDelay(50000); // microseconds
$cronark->start('worker');

// Using seconds (convenience method)
$cronark = new Cronark();
$cronark->setDelaySeconds(0.1); // 100ms
$cronark->start('worker');

// Method chaining
$cronark = new Cronark();
$cronark->setDelay(50000)
    ->addJob(EmailJob::class)
    ->start('worker');

When to use delay:

  • โœ… Shared hosting with CPU limits
  • โœ… Light/fast jobs (< 10ms execution time)
  • โœ… Rate limiting external API calls
  • โœ… Reducing database connection pressure

When NOT to use delay:

  • โŒ Heavy/slow jobs (they already have natural delay)
  • โŒ Real-time processing requirements
  • โŒ Time-sensitive operations

Recommended values:

  • 0 โ€“ No delay (default, maximum speed)
  • 10000 โ€“ 10ms (~100 jobs/sec, near real-time)
  • 50000 โ€“ 50ms (~20 jobs/sec, balanced)
  • 100000 โ€“ 100ms (~10 jobs/sec, CPU friendly)

๐Ÿ“– How It Works

Architecture

  • Cron Trigger: Cron executes worker script every minute (or your interval)
  • Duplicate Prevention: Worker checks if it's already running (via PID + script path)
  • Infinite Loop: If not running, worker enters infinite loop
  • Job Execution: Executes jobs sequentially in a circular fashion
  • Graceful Shutdown: Monitors PID to allow graceful termination

Process Flow

Cron executes worker.php
    โ†“
Check if worker already active?
    โ†“ No
Register PID and start infinite loop
    โ†“
Execute Job 1 โ†’ Job 2 โ†’ Job 3 โ†’ Job 1 โ†’ ...
    โ†“
Check PID still valid after each job
    โ†“
Continue until killed or error

Job Wrapping

Jobs execute in a circular fashion:

$cronark->addJob(JobA::class, 'worker');
$cronark->addJob(JobB::class, 'worker');
$cronark->addJob(JobC::class, 'worker');

// Execution order: A โ†’ B โ†’ C โ†’ A โ†’ B โ†’ C โ†’ A โ†’ ...

This ensures all jobs get executed repeatedly without any sitting idle.

๐Ÿ”ง Advanced Usage

Jobs can throw exceptions; they won't stop the worker:

Error Handling

class RiskyJob implements Job
{
    public function handle(): void
    {
        try {
            // Risky operation
            $this->processData();
        } catch (Exception $e) {
            // Handle error
            Log::error($e->getMessage());
            throw $e; // Worker will catch and continue
        }
    }
}

Worker Isolation

Each worker maintains its own state:

// worker-email.php
$cronark = new Cronark();
$cronark->addJob(SendEmailsJob::class, 'email');
$cronark->start('email');

// worker-data.php
$cronark = new Cronark();
$cronark->addJob(ProcessDataJob::class, 'data');
$cronark->start('data');

// Both can run simultaneously without conflict

Cron Schedule Examples

# Every minute
* * * * * php /path/to/worker-email.php

# Every 5 minutes
*/5 * * * * php /path/to/worker-data.php

# Every hour
0 * * * * php /path/to/worker-cleanup.php

# Every day at 2 AM
0 2 * * * php /path/to/worker-reports.php

# Multiple workers
* * * * * php /path/to/worker.php email
*/5 * * * * php /path/to/worker.php data
0 * * * * php /path/to/worker.php cleanup

Deployment on Shared Hosting

Most shared hosting providers (cPanel, Plesk) offer cron job access:

cPanel:

  1. Go to "Cron Jobs"
  2. Add: * * * * * php /home/username/public_html/worker.php

Plesk:

  1. Go to "Scheduled Tasks"
  2. Add command: php /var/www/vhosts/domain.com/worker.php
  3. Set schedule: Every minute

๐Ÿงช Testing

# Install dev dependencies
composer install

# Run all tests
composer test

# Run specific test suite
composer test:unit
composer test:integration

# Generate coverage report
composer test:coverage

Test Coverage: 67 tests with full coverage of all core functionality.

๐Ÿ›ก๏ธ Reliability

Duplicate Prevention

Cronark uses PID + Script Path verification to prevent duplicate workers:

// If worker already running
if ($cronark->isActive('email')) {
    echo "Worker already running, aborting";
    return; // Cron job exits
}

// Otherwise, start worker
$cronark->start('email');

Crash Recovery

If worker crashes, cron will restart it on next trigger:

Worker crashes at 10:05:23
    โ†“
Cron triggers at 10:06:00
    โ†“
Detects worker not running (PID check fails)
    โ†“
Starts new worker automatically

Atomic Storage

File operations use LOCK_EX for atomic writes:

// Prevents race conditions between workers
file_put_contents($file, $data, LOCK_EX);

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

# Clone repository
git clone https://github.com/nabeghe/cronark.git
cd cronark

# Install dependencies
composer install

# Run tests
composer test

๐Ÿ’ก Use Cases

Perfect for:

  • Shared Hosting Projects: No Redis/RabbitMQ required
  • Small to Medium Apps: When full queue infrastructure is overkill
  • Email Processing: Send newsletters, notifications
  • Data Import/Export: Process CSV files, API sync
  • Report Generation: Periodic reports, analytics
  • Cleanup Tasks: Temp file cleanup, log rotation
  • Social Media Posting: Schedule posts, fetch feeds
  • Database Maintenance: Backups, optimization
  • Any Recurring Task: If it needs to run regularly, Cronark can handle it

๐Ÿ“š Documentation

Core Classes

  • Cronark: Main scheduler class
  • Job: Interface for all jobs
  • Process: Cross-platform process utilities
  • Storage: File-based storage implementation
  • StorageInterface: Storage contract for custom backends

Key Methods

// Worker management
$cronark->registerWorker(string $worker): void
$cronark->start(string $worker): void
$cronark->isActive(string $worker, ?int &$pid = null): bool
$cronark->kill(string $worker, ?int &$pid = null): bool
$cronark->killAll(): void

// Job management
$cronark->addJob(string $job, string $worker = 'main', int $position = -1): void
$cronark->getJobsCount(?string $worker = null): int
$cronark->hasAnyJob(?string $worker = null): bool

// State management
$cronark->getPid(string $worker): ?int
$cronark->setPid(?int $pid, string $worker): bool
$cronark->getCurrentWorker(): ?string

โš ๏ธ Important Notes

  1. Execution Time: Workers run indefinitely; ensure your hosting allows long-running processes
  2. Memory: Monitor memory usage if jobs process large datasets
  3. Error Handling: Always implement proper error handling in jobs
  4. Logging: Use the print() method or implement custom logging
  5. Database Connections: Reconnect to database in onJobCreating() hook to avoid timeout issues

๐ŸŒŸ Show Your Support

If you find Cronark useful, please:

  • โญ Star the repository
  • ๐Ÿ› Report bugs
  • ๐Ÿ’ก Suggest features
  • ๐Ÿ“– Improve documentation
  • ๐Ÿ”€ Submit pull requests

๐Ÿ“– License

Licensed under the MIT license, see LICENSE.md for details.

Made with โค๏ธ by (Nabeghe)[https://github.com/nabeghe]

Cronark - Simple, Reliable, Zero-Dependency Background Jobs for PHP