Framework-agnostic PHP core for Crontinel. Provides monitor contracts, typed data objects, alert management with deduplication, and cron expression helpers.
If you use Laravel, install crontinel/laravel instead. It pulls in this package automatically and adds service providers, dashboard, Artisan commands, and event listeners.
Install crontinel/php directly when you are:
- Building an adapter for Symfony, Slim, or another framework
- Integrating Crontinel into a vanilla PHP application
- Writing a custom monitor that does not depend on Laravel
- PHP 8.2, 8.3, or 8.4
- Composer
- Laravel 10, 11, and 12 — install
crontinel/laravelinstead, which wraps this package and adds Laravel-specific integration. - Horizon — this package reads the same Redis keys that Horizon uses. When you install
crontinel/laravel, theHorizonMonitorworks out of the box with no additional configuration. - Other frameworks — Symfony, Slim, and any PHP 8.2+ application can use the contracts and data objects directly.
composer require crontinel/phpMonitorInterface-- implementisHealthy(): boolto create a custom monitorAlertChannelInterface-- implementsend(AlertEvent $event): voidto deliver alerts via any transport (Slack, email, webhook, etc.)
Immutable, readonly value objects used across the stack:
AlertEvent-- key, title, message, level (critical/warning/info/resolved), fired timestampCronStatus-- command, expression, status, last run metadata, next due timeHorizonStatus-- Horizon supervisor stateQueueStatus-- queue depth, failed count, oldest job age
AlertManager handles fire/resolve lifecycle with built-in deduplication. It accepts any PSR-16 cache and any AlertChannelInterface implementation:
- Deduplicates repeated alerts for the same key (default: 5 min TTL)
- Auto-sends "resolved" notifications when an issue clears
- Logs failures via PSR-3 logger (falls back to
NullLogger)
CronExpressionHelper wraps dragonmantank/cron-expression with convenience methods:
nextDue(string $expression)-- next scheduled run asDateTimeImmutablepreviousDue(string $expression)-- last scheduled runisLate(DateTimeInterface $lastRunAt, string $expression, int $graceSeconds = 120)-- whether a job has missed its window
Ready-to-use monitor implementations (all implement MonitorInterface):
CronMonitor,HorizonMonitor,QueueMonitor
Implement MonitorInterface to build a custom health check:
use Crontinel\Contracts\MonitorInterface;
final class DiskSpaceMonitor implements MonitorInterface
{
public function __construct(
private readonly string $path = '/',
private readonly float $minFreePercent = 10.0,
) {}
public function isHealthy(): bool
{
$free = disk_free_space($this->path);
$total = disk_total_space($this->path);
return ($free / $total) * 100 >= $this->minFreePercent;
}
}Wire up alert delivery by implementing AlertChannelInterface:
use Crontinel\Contracts\AlertChannelInterface;
use Crontinel\Data\AlertEvent;
final class WebhookChannel implements AlertChannelInterface
{
public function __construct(
private readonly string $url,
) {}
public function send(AlertEvent $event): void
{
file_get_contents($this->url, false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode([
'key' => $event->key,
'title' => $event->title,
'message' => $event->message,
'level' => $event->level,
'resolved' => $event->resolved,
]),
],
]));
}
}Then use AlertManager to fire and resolve alerts:
use Crontinel\Alert\AlertManager;
// Requires any PSR-16 cache implementation
$alertManager = new AlertManager(
cache: $cache,
channel: new WebhookChannel('https://example.com/webhook'),
);
$monitor = new DiskSpaceMonitor('/data');
if (! $monitor->isHealthy()) {
$alertManager->fire('disk.data', 'Low disk space', 'Disk /data is below 10% free', 'critical');
} else {
$alertManager->resolve('disk.data');
}use Crontinel\Cron\CronExpressionHelper;
$helper = new CronExpressionHelper();
// When is the next run of "every minute"?
$next = $helper->nextDue('* * * * *');
echo $next->format('Y-m-d H:i:s');
// Is a job that's been silent for 10 minutes late for a 5-minute schedule?
$lastRun = new \DateTimeImmutable('2026-04-11 10:50:00');
$isLate = $helper->isLate($lastRun, '*/5 * * * *', graceSeconds: 60);
// Returns true if current time exceeds $lastRun + 5min + 60s graceuse Crontinel\Alert\AlertManager;
use Crontinel\Monitors\QueueMonitor;
$alertManager = new AlertManager(
cache: $cache,
channel: new WebhookChannel('https://example.com/alerts'),
);
$queueMonitor = new QueueMonitor(
redisConnection: 'default',
maxDepth: 100,
maxFailed: 10,
);
if (! $queueMonitor->isHealthy()) {
$status = $queueMonitor->getStatus(); // returns QueueStatus object
$alertManager->fire(
'queue.depth',
'Queue depth exceeded',
"Queue has {$status->depth} jobs (max: 100)",
'warning',
);
}| Package | Description |
|---|---|
| crontinel/php (this repo) | Framework-agnostic PHP core |
| crontinel/laravel | Laravel package with dashboard, Artisan commands, and event listeners |
| crontinel/mcp-server | MCP server for AI assistants (Claude, Cursor) |
| docs.crontinel.com | Full documentation |
| app.crontinel.com | Hosted SaaS dashboard |
MIT. See LICENSE.
Built by Harun R Rayhan · crontinel.com