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.
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
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
Install via Composer:
composer require nabeghe/cronarkRequirements:
- PHP 8.1 or higher
- Cron access (available on virtually all hosting providers)
<?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!');
}
}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');Add to your crontab:
# Run email worker every minute
* * * * * php /path/to/worker.phpThat's it! Your jobs will now run continuously in the background.
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);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();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 -> repeatImplement 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());- File-based (default): Zero setup, works everywhere
- Custom: Implement StorageInterface for database, Redis, etc.
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
}
}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');- β Shared hosting with CPU limits
- β Light/fast jobs (< 10ms execution time)
- β Rate limiting external API calls
- β Reducing database connection pressure
- β Heavy/slow jobs (they already have natural delay)
- β Real-time processing requirements
- β Time-sensitive operations
- 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)
- 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
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
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.
Jobs can throw exceptions; they won't stop the worker:
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
}
}
}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# 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 cleanupMost shared hosting providers (cPanel, Plesk) offer cron job access:
- Go to "Cron Jobs"
- Add: * * * * * php /home/username/public_html/worker.php
- Go to "Scheduled Tasks"
- Add command: php /var/www/vhosts/domain.com/worker.php
- Set schedule: Every minute
# 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:coverageTest Coverage: 67 tests with full coverage of all core functionality.
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');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
File operations use LOCK_EX for atomic writes:
// Prevents race conditions between workers
file_put_contents($file, $data, LOCK_EX);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 testPerfect 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
Cronark: Main scheduler classJob: Interface for all jobsProcess: Cross-platform process utilitiesStorage: File-based storage implementationStorageInterface: Storage contract for custom backends
// 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- 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
If you find Cronark useful, please:
- β Star the repository
- π Report bugs
- π‘ Suggest features
- π Improve documentation
- π Submit pull requests
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