1monitor / sdk-php
Official PHP SDK for 1Monitor — ping your cron jobs, workers and scheduled tasks.
Requires
- php: >=8.1
- guzzlehttp/guzzle: ^7.0 || ^8.0
- guzzlehttp/psr7: ^2.6 || ^3.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/log: ^1.1 || ^2.0 || ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0 || ^13.0
- squizlabs/php_codesniffer: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-07 02:37:59 UTC
README
Official PHP SDK for 1Monitor — ping your cron jobs, queue workers and scheduled tasks so you find out when they stop running.
$client = new OneMonitor\Sdk\Client(); $client->ping('your-monitor-token');
Sending a ping never throws and never blocks for long. Monitoring must not be the thing that takes your job down. See The no-throw guarantee.
Requirements
PHP 8.1 or newer.
Install
composer require 1monitor/sdk-php:^0.4
Quickstart
Create a monitor in 1Monitor, copy its ping token, and call the SDK from the job you want watched.
use OneMonitor\Sdk\Client; $client = new Client(); $client->ping('tok_abc'); // "this job is alive"
That single call is enough for a heartbeat monitor: 1Monitor alerts you when the pings stop arriving.
Measuring how long a run takes
If you also want to know that a job finished, and how long it took, wrap the work in a start/finish pair. 1Monitor pairs them into a run, records its duration, and can alert on runs that fail or overrun.
use OneMonitor\Sdk\Client; $client = new Client(); $token = 'tok_abc'; $client->pingStart($token); try { doTheWork(); $client->pingSuccess($token); } catch (Throwable $e) { $client->pingFail($token, output: $e->getMessage()); throw $e; }
Put the pingFail before the rethrow, not in a finally, so a crash still closes the run — and so the exception keeps propagating exactly as it would without the SDK.
Saying why it failed
ping, pingSuccess and pingFail optionally carry the job's exit code and output, so the alert can tell you what went wrong instead of just that something did:
$exitCode = 0; $stderr = ''; // ... run the job, capturing $exitCode and $stderr ... if ($exitCode === 0) { $client->pingSuccess($token, exitCode: $exitCode); } else { $client->pingFail($token, exitCode: $exitCode, output: $stderr); }
The exit code travels as ?exit_code=N; a non-zero value marks the ping as failed even on a bare ping(). The output travels as the request body and is capped at 10 KB (Client::MAX_OUTPUT_BYTES) — the SDK truncates longer output before sending, because that is all the server would keep anyway.
1Monitor sweeps common credential shapes (tokens, API keys, private keys) out of ping output before storing it, but that is a safety net, not permission: send your job's diagnostic output, not its environment or its config.
From a cron job
The same pattern as a standalone script, wired into your crontab:
#!/usr/bin/env php <?php // bin/nightly-report require __DIR__ . '/../vendor/autoload.php'; use OneMonitor\Sdk\Client; $client = new Client(); $token = getenv('ONEMONITOR_TOKEN') ?: ''; $client->pingStart($token); try { (new NightlyReport())->run(); $client->pingSuccess($token); } catch (Throwable $e) { $client->pingFail($token); fwrite(STDERR, $e->getMessage() . PHP_EOL); exit(1); }
0 3 * * * ONEMONITOR_TOKEN=tok_abc /srv/app/bin/nightly-report
Keep the token in the environment rather than in the source — it is a credential: anyone holding it can ping your monitor.
The four calls
| Method | URL | Meaning |
|---|---|---|
ping($token, ?$exitCode, ?$output) |
/ping/{token} |
The job is alive. |
pingStart($token) |
/ping/{token}/start |
A run has started. |
pingSuccess($token, ?$exitCode, ?$output) |
/ping/{token}/success |
The open run finished successfully. |
pingFail($token, ?$exitCode, ?$output) |
/ping/{token}/fail |
The open run failed. |
Each returns bool: true when 1Monitor accepted the ping, false when it did not.
exitCode and output are optional and named. A ping with output is sent as a POST with the output as the body; without one it stays a GET. pingStart carries neither — the job has not produced a result yet. Exit codes outside 0–255 are ignored by the server.
Options
Every constructor option is optional and named:
$client = new OneMonitor\Sdk\Client( timeout: 2.0, retries: 1, logger: $psrLogger, );
| Option | Type | Default | What it does |
|---|---|---|---|
baseUrl |
string |
https://ping.1monitor.io |
Where pings are sent. An http/https URL with an optional path prefix; credentials, a query string or a fragment are rejected. |
timeout |
float |
5.0 |
Seconds of network time shared across all attempts — see below. |
retries |
int |
2 |
Extra attempts after the first one fails. 0 disables retrying. |
logger |
?Psr\Log\LoggerInterface |
none | Where failed pings are reported. Without one, failures are silent. |
httpClient |
?Psr\Http\Client\ClientInterface |
a plain Guzzle client | Any PSR-18 client — for a proxy, custom TLS, or a different HTTP stack. A Guzzle client gets extra care — see below. |
requestFactory |
?Psr\Http\Message\RequestFactoryInterface |
guzzlehttp/psr7 |
Builds the ping requests (PSR-17). |
streamFactory |
?Psr\Http\Message\StreamFactoryInterface |
guzzlehttp/psr7 |
Builds the output body streams (PSR-17). |
Maximum time a ping can hold up your job: timeout + backoff sleeps — about 6.5 s with the defaults (5 s of network time, plus 0.5 s and 1 s between the three attempts).
Why timeout is a budget, not a per-attempt limit
timeout is an overall deadline for time spent on the wire. Attempts draw it down as they run, and no retry starts once the budget is gone.
That is deliberate. If timeout only limited single attempts, three attempts against a hanging endpoint would block for 3 × 5 s plus backoff — around 16.5 s of your job's runtime spent on monitoring. The shared budget keeps the worst case flat no matter how many retries you allow.
Guzzle clients vs. other PSR-18 clients
The SDK drives a Guzzle client — the default, or any injected one — through Guzzle's own request API rather than its PSR-18 adapter, because the adapter supports neither per-request timeouts nor redirects. With Guzzle you therefore get the full guarantees: each attempt's socket timeout is the remaining budget (so the 6.5 s worst case above holds), and redirects are followed.
Guzzle receives these as per-request options, so they take precedence over whatever the injected client was configured with. A Guzzle client follows at most 5 redirects per attempt and, when baseUrl is https, refuses a redirect to http so the token is never sent in clear text.
With a non-Guzzle PSR-18 client, two things are yours to handle:
- Socket timeouts — PSR-18 has no per-request options, so configure them on the client. The SDK still refuses to start a retry once the budget is spent, but it cannot cut short an attempt already in flight; a hanging endpoint can then hold the job for one full attempt beyond the budget.
- Redirects — followed only if your client follows them. A
3xxreaching the SDK counts as a failed ping and is not retried, so pointbaseUrldirectly at the ping endpoint overhttps.
What gets retried
Only connection failures and 5xx responses. A 4xx is a decision, not a hiccup — a wrong token stays wrong, and a 429 means you are already sending too much — so those fail immediately. Backoff is roughly 0.5 s, then 1 s.
Ping constraints
If a monitor requires a specific HTTP method or header, inject an HTTP client configured with them — any PSR-18 client works, Guzzle shown here:
$client = new OneMonitor\Sdk\Client( httpClient: new GuzzleHttp\Client([ 'timeout' => 5.0, 'headers' => ['X-Deploy-Key' => 'secret'], ]), );
First-class options for this may be added later.
The no-throw guarantee
The SDK exists to tell you when a job stopped working. It must never be the reason a job stops working.
So delivering a ping never throws. A DNS failure, a dead network, a TLS error, an unreachable 1Monitor, a 404 from a deleted monitor, a 429, a 500 — all of them return false. Nothing propagates into your job.
if (!$client->pingSuccess($token)) { // The work is done regardless. Whether you care is up to you. }
Failures are reported to the PSR-3 logger you pass in, at error level. The context carries:
| Key | Value |
|---|---|
state |
ping, start, success or fail |
baseUrl |
The configured base URL |
attempts |
How many attempts were made |
status |
The last HTTP status, or null on a transport failure |
error |
The class of the last exception, or null |
reason |
Its message, or null |
The ping token is a credential and is deliberately kept out of the log: the exception object itself is not passed on, because HTTP clients embed the request URL in it, and the token is redacted from the message. Without a logger, failures are dropped silently.
The only exceptions the SDK throws are your own mistakes, caught early and loudly:
| Thrown | When |
|---|---|
OneMonitor\Sdk\Exception\InvalidArgumentException |
Malformed baseUrl, non-positive timeout, or negative retries — at construction. |
OneMonitor\Sdk\Exception\InvalidArgumentException |
Empty ping token — at the call. |
Both implement OneMonitor\Sdk\Exception\Exception, so catch (OneMonitor\Sdk\Exception\Exception $e) catches everything this package throws.
Testing code that pings
Client is final. Type-hint against OneMonitor\Sdk\ClientInterface instead and substitute a test double in your own tests:
use OneMonitor\Sdk\ClientInterface; final class NightlyReport { public function __construct(private ClientInterface $monitor) { } }
The interface declares the same four methods as Client.
Security
- The ping token is a credential. Keep it in the environment or a secret store, never in source. It never reaches the SDK's log output.
- Use
https. The default base URL ishttps, TLS certificates are verified, and a Guzzle client will not follow a redirect that downgrades tohttp. Anhttpbase URL is accepted for local or self-hosted setups; the token then travels in clear text. - Output is sent as is. Send diagnostic output, not the job's environment or configuration. 1Monitor sweeps common credential shapes out of stored output, but treat that as a safety net.
- Reporting. Vulnerabilities go through the private process in SECURITY.md, not the public issue tracker.
Versioning
This package is at 0.x, and Composer treats 0.x minors as breaking. Require it as:
"1monitor/sdk-php": "^0.4"
The version is deliberately below 1.0.0: the SDK's stability promise cannot exceed that of the ping API it wraps, which is not yet formally versioned. 1.0.0 follows once it is.
Changes are recorded in CHANGELOG.md.
Scope
Ping only. Management operations — creating, listing or pausing monitors, reading alerts — are not covered; there is no public management API yet. They will be added to this same package when there is, which is why it is named sdk-php and not ping-client.
Contributing
Bug reports and feature requests are welcome in GitHub Issues. Security issues follow a separate process — see SECURITY.md.
See CONTRIBUTING.md for the development setup, the checks CI runs, and the design constraints a change must respect.
License
MIT — see LICENSE.