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
Requires
- php: >=8.1
Requires (Dev)
- phpunit/phpunit: ^12.5
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:
- Go to "Cron Jobs"
- Add: * * * * * php /home/username/public_html/worker.php
Plesk:
- Go to "Scheduled Tasks"
- Add command: php /var/www/vhosts/domain.com/worker.php
- 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 classJob: Interface for all jobsProcess: Cross-platform process utilitiesStorage: File-based storage implementationStorageInterface: 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
- Execution Time: Workers run indefinitely; ensure your hosting allows long-running processes
- Memory: Monitor memory usage if jobs process large datasets
- Error Handling: Always implement proper error handling in jobs
- Logging: Use the
print()method or implement custom logging - 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