PHPackages                             cron-monitor/php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. cron-monitor/php-sdk

ActiveLibrary[HTTP &amp; Networking](/categories/http)

cron-monitor/php-sdk
====================

PHP SDK for cronheart.com (cron-monitor) with first-class Symfony Scheduler and Laravel Scheduler support.

v0.2.1(3w ago)0340↓100%1MITPHPPHP &gt;=8.2CI passing

Since May 14Pushed 2w agoCompare

[ Source](https://github.com/alexander-po/cron-monitor-php)[ Packagist](https://packagist.org/packages/cron-monitor/php-sdk)[ Docs](https://cronheart.com)[ RSS](/packages/cron-monitor-php-sdk/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (19)Versions (15)Used By (1)

cron-monitor PHP SDK
====================

[](#cron-monitor-php-sdk)

Composer SDK for [cronheart.com](https://cronheart.com) — heartbeat monitoring for scheduled jobs. Get pinged when a cron / systemd timer / scheduler entry stops checking in on time. First-class support for **Symfony Scheduler** and the **Laravel scheduler**.

[![CI](https://github.com/alexander-po/cron-monitor-php/actions/workflows/ci.yml/badge.svg)](https://github.com/alexander-po/cron-monitor-php/actions/workflows/ci.yml)[![Latest Version](https://camo.githubusercontent.com/2cde8f0f1e29f44ebb932c4e4c140e1d4ebabd80526ad5d135ffa05f8eae053b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f63726f6e2d6d6f6e69746f722f7068702d73646b2e737667)](https://packagist.org/packages/cron-monitor/php-sdk)[![Monthly Downloads](https://camo.githubusercontent.com/a151d25c8bb5a38f76baf6f97a30bbc907c79f0231f57b5ff306b2aee8490707/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646d2f63726f6e2d6d6f6e69746f722f7068702d73646b2e737667)](https://packagist.org/packages/cron-monitor/php-sdk)[![PHP Version](https://camo.githubusercontent.com/e088fcd9e3c23821e1a6068c6d64b08f19678edf19fd0be1b0cecb711f76bfcb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f63726f6e2d6d6f6e69746f722f7068702d73646b2e737667)](https://packagist.org/packages/cron-monitor/php-sdk)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](LICENSE)

Why
---

[](#why)

Uptime monitors don't catch the silent failure mode: a backup that stopped running a month ago, an invoice job that didn't fire on the 1st, an ETL pipeline whose systemd timer was renamed. cronheart's per-job dead-man switch does. This SDK takes the boilerplate out of wiring it up.

What's in the box
-----------------

[](#whats-in-the-box)

- **Drop-in Symfony bundle** — auto-registered; `bin/console cron-monitor:sync`inventories Scheduler `RecurringMessage` providers and console commands.
- **Drop-in Laravel package** — auto-discovered service provider; `Schedule::command(...)->monitor()` macro and `MonitorQueueJob`middleware for `ShouldQueue` jobs.
- **`#[Monitor(uuid: ...)]` attribute** — the UUID lives on the command class instead of being duplicated in YAML / config. Works on both Symfony Console and Laravel Artisan commands.
- **Zero extra dependencies** — bundled cURL PSR-18 transport and `nyholm/psr7` factories. Bring Guzzle or `symfony/http-client` if you want connection pooling; otherwise `composer require` is enough.
- **Never breaks the host job** — every network / HTTP error becomes a `PingResult::failed(...)` return value. A broken cron-monitor backend cannot punish your scheduler.

Install
-------

[](#install)

```
composer require cron-monitor/php-sdk
```

PHP ≥ 8.2 with `ext-curl` (the bundled cURL transport uses it; almost every PHP install has it on).

`composer require` is the only step. The SDK ships with its own minimal cURL PSR-18 transport plus the PSR-17 factories from the bundled `nyholm/psr7` — no Guzzle / symfony/http-client required.

- **Symfony bundle path** — drop-in. The bundle prefers `symfony/http-client`'s `Psr18Client` if present and falls back to the bundled cURL transport otherwise. Your own PSR-17 / PSR-18 bindings always win.
- **Laravel path** — auto-discovered. The service provider uses bindings from the container when present and falls back to the bundled cURL transport + `nyholm/psr7` factories.
- **Framework-agnostic path** (plain PHP / Slim / etc.) — use `CronMonitorClient::create()` (see Quick start). Want to plug in Guzzle or symfony/http-client? Construct `CronMonitorClient` directly and pass your PSR-18 client + PSR-17 factory.

Quick start (framework-agnostic)
--------------------------------

[](#quick-start-framework-agnostic)

```
use CronMonitor\Client\CronMonitorClient;

CronMonitorClient::create()->success('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx');
```

Wrap a long-running job:

```
use CronMonitor\Client\CronMonitorClient;

$client = CronMonitorClient::create();
$uuid   = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';

$client->start($uuid);
try {
    runMyImportantJob();
    $client->success($uuid);
} catch (\Throwable $e) {
    $client->fail($uuid, $e->getMessage());
    throw $e;
}
```

The client **never throws** on network or HTTP errors — a broken cron-monitor backend will not break your job.

For a custom endpoint (staging / preview), tighter timeout, or API key:

```
use CronMonitor\Client\Configuration;
use CronMonitor\Client\CronMonitorClient;

$client = CronMonitorClient::create(
    new Configuration(
        endpoint:       'https://staging.cronheart.com',
        timeoutSeconds: 3.0,
        retries:        2,
        apiKey:         getenv('CRON_MONITOR_API_KEY') ?: null,
    ),
);
```

Symfony Scheduler integration
-----------------------------

[](#symfony-scheduler-integration)

Register the bundle (Flex usually does this for you):

```
// config/bundles.php
return [
    // ...
    CronMonitor\Bridge\Symfony\CronMonitorBundle::class => ['all' => true],
];
```

Configure:

```
# config/packages/cron_monitor.yaml
cron_monitor:
    endpoint:        '%env(CRON_MONITOR_ENDPOINT)%'  # optional, defaults to SaaS
    timeout_seconds: 5.0
    retries:         1
    api_key:         '%env(CRON_MONITOR_API_KEY)%'   # optional
    messages:
        # FQCN of any message your Scheduler dispatches via Messenger.
        # The middleware ships start/success/fail pings for each one.
        App\Scheduler\Message\NightlyReportRun: 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
```

Discover the FQCNs the bundle can see:

```
php bin/console cron-monitor:sync
```

It prints every `RecurringMessage` from every tagged `scheduler.schedule_provider`, plus a YAML snippet you can paste into the config above.

### Plain `bin/console` commands

[](#plain-binconsole-commands)

If your cron entry is a console command rather than a Scheduler run — e.g. `* * * * * php bin/console app:reports:nightly` straight out of crontab — either map the command name in YAML **or** declare the monitor UUID right on the command class, and the bundle wraps every invocation in start/success/fail pings via a kernel event subscriber.

YAML map (good for "configuration as deploy artefact" workflows):

```
cron_monitor:
    commands:
        'app:reports:nightly': 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
```

Class attribute (good for "the UUID lives next to the code"):

```
use CronMonitor\Attribute\Monitor;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;

#[AsCommand(name: 'app:reports:nightly')]
#[Monitor(env: 'CRON_MONITOR_REPORTS_NIGHTLY_UUID')]
final class GenerateNightlyReportCommand extends Command
{
    // ...
}
```

Two attribute forms are supported:

- `#[Monitor(env: 'VAR_NAME')]` — **recommended for production.** The attribute carries the env-var *name*; the SDK resolves the value at runtime via `$_ENV` → `$_SERVER` → `getenv()`. The UUID — a write capability secret — stays out of git history and rotates without a redeploy.
- `#[Monitor(uuid: '...literal...')]` — convenient for local dev or one-off scripts where the UUID is not sensitive.

(`#[Monitor(uuid: getenv('VAR'))]` is a PHP parse error — attribute arguments must be compile-time constants — and `'%env(VAR)%'` is not expanded inside attribute payloads. The two-parameter shape is the intended way to thread env-sourced UUIDs into the attribute path.)

Both attribute sources are honoured, and **YAML wins on conflict** — so you can override either form per environment in YAML (e.g. `'app:reports:nightly': '%env(MY_UUID)%'` with `MY_UUID` blank in dev to suppress monitoring without touching the attribute). A missing or empty env var is itself treated as deliberate suppression — same policy as an empty YAML map entry.

No code changes inside the command body. A non-zero exit fires `fail`; an uncaught throwable fires `fail` with the exception class, message, and file:line in the body so the cron-monitor dashboard shows the immediate cause without you tailing logs.

Laravel scheduler integration
-----------------------------

[](#laravel-scheduler-integration)

The service provider is auto-discovered. Publish the config:

```
php artisan vendor:publish --tag=cron-monitor-config
```

Then in `routes/console.php`, either pass the UUID at the call site **or** declare it on the command class and call `->monitor()` without arguments:

```
use Illuminate\Support\Facades\Schedule;

// Explicit UUID
Schedule::command('reports:nightly')
    ->dailyAt('02:00')
    ->monitor('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx');

// UUID lives on the command class
use App\Console\Commands\GenerateNightlyReportCommand;

Schedule::command(GenerateNightlyReportCommand::class)
    ->dailyAt('02:00')
    ->monitor();
```

For the no-arg form, the macro reads the `#[Monitor]` attribute on the Artisan command class behind the scheduled event. The attribute has two construction forms — env-sourced (recommended for production) and literal:

```
use CronMonitor\Attribute\Monitor;
use Illuminate\Console\Command;

// Recommended for production — UUID stays out of git history.
#[Monitor(env: 'CRON_MONITOR_REPORTS_NIGHTLY_UUID')]
final class GenerateNightlyReportCommand extends Command
{
    protected $signature = 'reports:nightly';

    // ...
}

// Or with a literal UUID — fine for local dev / one-off scripts.
#[Monitor(uuid: 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx')]
```

The env-sourced form resolves through `$_ENV` → `$_SERVER` → `getenv()` so it works across CLI, FPM, and container-injected env setups. A missing or empty env var is treated as deliberate suppression for the current environment.

Precedence rule: an explicit string argument to `->monitor(...)` always wins over the attribute, and an empty string (`->monitor(env('MY_UUID', ''))`) is treated as explicit suppression for the current environment. The `->monitor(...)` macro hooks `before` / `onSuccess` / `onFailure` so you get start/success/fail pings on the same boundary as the job execution.

`php artisan cron-monitor:sync` lists every scheduled command and emits a `config/cron-monitor.php` snippet.

### Queued jobs

[](#queued-jobs)

For `ShouldQueue` jobs dispatched outside the scheduler, attach the bundled job middleware:

```
use CronMonitor\Bridge\Laravel\Queue\MonitorQueueJob;

class GenerateNightlyReport implements ShouldQueue
{
    public function middleware(): array
    {
        return [MonitorQueueJob::withUuid('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx')];
    }
}
```

Each invocation pings `start`, then `success` on completion or `fail` on a thrown exception (with the class, message, and file:line in the body). The underlying SDK swallows its own failures, so a flaky cron-monitor backend never breaks the queued job.

Standalone CLI
--------------

[](#standalone-cli)

For plain cron / systemd timer users, the `vendor/bin/cron-monitor`binary doesn't require any framework:

```
# Heartbeat from a one-liner cron entry
* * * * * /opt/scripts/run.sh && cron-monitor heartbeat $UUID

# Wrap a job with start/success/fail
cron-monitor start $UUID
if /opt/scripts/run.sh 2> /tmp/err; then
    cron-monitor success $UUID
else
    cron-monitor fail $UUID --body="$(cat /tmp/err)"
fi
```

Set `CRON_MONITOR_ENDPOINT` and `CRON_MONITOR_API_KEY` in the environment to avoid repeating the flags.

Configuration knobs
-------------------

[](#configuration-knobs)

SettingDefaultNotes`endpoint``https://cronheart.com`Self-hosted: point at your install.`timeout_seconds``5.0`Per-request, low by design.`retries``1`Pings are idempotent server-side.`api_key``null`Reserved for future authenticated routes.`allow_insecure_endpoint``false`Required for `http://` endpoints.Security
--------

[](#security)

- HTTPS is required by default. The SDK refuses to send pings to plain HTTP unless `allow_insecure_endpoint: true` is explicitly set.
- The per-monitor UUID is treated as a write credential and is validated against the canonical UUID v4 shape before being concatenated into a URL — no path traversal via the action segment.
- The `Authorization: Bearer ` header is attached only when an API key is configured; nothing is sent for anonymous installs.
- **`fail` pings include exception text and a host file path.** When a monitored handler throws, the SDK sends the exception class name, `getMessage()`, and `file:line` of the throw site to the cron-monitor endpoint (capped at 10 KB). Exception messages routinely embed attacker-controlled input (e.g. `PDOException` SQL fragments, validation errors echoing user data); the file path discloses the host deployment layout. If your threat model treats either as sensitive, wrap the host job in a `try`/`catch` that throws a sanitised exception, or call `CronMonitorClient::fail($uuid, $body)` directly with a curated body.

Development
-----------

[](#development)

```
composer install
composer test          # PHPUnit
composer stan          # PHPStan level 8
composer cs-check      # php-cs-fixer dry-run
```

License
-------

[](#license)

MIT — see [`LICENSE`](LICENSE).

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance96

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

6

Last Release

21d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/fd43420b199c03e5a942d9686872f6330c09e0a3ee2a3edaf45e85d2742acda8?d=identicon)[alexander-po](/maintainers/alexander-po)

---

Top Contributors

[![alexander-po](https://avatars.githubusercontent.com/u/10212431?v=4)](https://github.com/alexander-po "alexander-po (23 commits)")

---

Tags

schedulerconsolesymfonylaravelmonitoringpsr-18artisancronhealthcheckdeadman-switch

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/cron-monitor-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/cron-monitor-php-sdk/health.svg)](https://phpackages.com/packages/cron-monitor-php-sdk)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k11](/packages/tempest-framework)[cakephp/cakephp

The CakePHP framework

8.8k19.1M1.7k](/packages/cakephp-cakephp)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

84735.1k](/packages/flow-php-flow)[simplesamlphp/saml2

SAML2 PHP library from SimpleSAMLphp

30417.8M41](/packages/simplesamlphp-saml2)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6941.5M395](/packages/drupal-core-recommended)[web-auth/webauthn-framework

FIDO2/Webauthn library for PHP and Symfony Bundle.

51090.8k2](/packages/web-auth-webauthn-framework)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
