Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

All notable changes to `ohdear-php-sdk` will be documented in this file

## 4.6.0 - 2026-03-26

### What's Changed

* Chore | Upgrade to Saloon v4 by @Sammyjo20 in https://github.com/ohdearapp/ohdear-php-sdk/pull/70

### New Contributors

* @Sammyjo20 made their first contribution in https://github.com/ohdearapp/ohdear-php-sdk/pull/70

**Full Changelog**: https://github.com/ohdearapp/ohdear-php-sdk/compare/4.4.3...4.6.0

## Add warning state support - 2026-02-28

### What changed

- Added `CheckResult` backed string enum (`pending`, `succeeded`, `warning`, `failed`, `errored-or-timed-out`) with helper methods (`isUp()`, `isDown()`, `isPending()`, `isWarning()`)
- Added `checkResult()` method to `Check`, `Monitor`, and `CheckSummary` DTOs that returns the typed enum

## Add missing API endpoints - 2026-02-17

### What changed
Expand Down
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
![Tests](https://github.com/ohdearapp/ohdear-php-sdk/workflows/run-tests/badge.svg)
[![Total Downloads](https://img.shields.io/packagist/dt/ohdearapp/ohdear-php-sdk.svg?style=flat-square)](https://packagist.org/packages/ohdearapp/ohdear-php-sdk)

This package is the official PHP SDK for the [Oh Dear](https://ohdear.app) API, built with [Saloon](https://docs.saloon.dev/) v3.
This package is the official PHP SDK for the [Oh Dear](https://ohdear.app) API, built with [Saloon](https://docs.saloon.dev/) v4.

```php
use OhDear\PhpSdk\OhDear;
Expand Down Expand Up @@ -152,9 +152,51 @@ $checkSummary = $ohDear->checkSummary($monitorId, CheckType::CertificateHealth);

echo "Check result: {$checkSummary->result}\n";
echo "Summary: {$checkSummary->summary}\n";

// Use the checkResult() method to get the typed enum with helper methods
if ($checkSummary->checkResult()->isUp()) {
echo "Monitor is reachable\n";
}

if ($checkSummary->checkResult()->isWarning()) {
echo "Partial connectivity issue detected\n";
}

if ($checkSummary->checkResult()->isDown()) {
echo "Monitor is down\n";
}
```

You can request a summary for all available cases in the `CheckType` enum.`
You can request a summary for all available cases in the `CheckType` enum.

#### Check result enum

The `CheckResult` enum represents the possible states of a check. Access it via the `checkResult()` method available on `Check`, `Monitor`, and `CheckSummary` DTOs:

```php
use OhDear\PhpSdk\Enums\CheckResult;

$checkSummary = $ohDear->checkSummary($monitorId, CheckType::Uptime);

// The raw string is still available
echo $checkSummary->result; // 'succeeded', 'warning', 'failed', etc.

// Use checkResult() for the typed enum
$result = $checkSummary->checkResult();

// Available cases:
CheckResult::Pending; // 'pending'
CheckResult::Succeeded; // 'succeeded'
CheckResult::Warning; // 'warning' — primary location reports down, secondary confirms reachable
CheckResult::Failed; // 'failed'
CheckResult::ErroredOrTimedOut; // 'errored-or-timed-out'

// Helper methods:
$result->isUp(); // true for Succeeded and Warning
$result->isDown(); // true for Failed and ErroredOrTimedOut
$result->isPending(); // true for Pending only
$result->isWarning(); // true for Warning only
```

#### Getting certificate health for a monitor

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"require": {
"php": "^8.1",
"saloonphp/pagination-plugin": "^2.2",
"saloonphp/saloon": "^3.14"
"saloonphp/saloon": "^4.0"
},
"require-dev": {
"laravel/pint": "^1.27",
Expand Down
11 changes: 11 additions & 0 deletions src/Dto/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace OhDear\PhpSdk\Dto;

use OhDear\PhpSdk\Enums\CheckResult;

class Check
{
public function __construct(
Expand All @@ -17,6 +19,15 @@ public function __construct(
public ?array $activeSnooze,
) {}

public function checkResult(): ?CheckResult
{
if ($this->latestRunResult === null) {
return null;
}

return CheckResult::tryFrom($this->latestRunResult);
}

public static function fromResponse(array $data): self
{
return new self(
Expand Down
17 changes: 14 additions & 3 deletions src/Dto/CheckSummary.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@

namespace OhDear\PhpSdk\Dto;

use OhDear\PhpSdk\Enums\CheckResult;

class CheckSummary
{
public function __construct(
public string $result,
public ?string $result,
public ?string $summary,
) {}

public function checkResult(): ?CheckResult
{
if ($this->result === null) {
return null;
}

return CheckResult::tryFrom($this->result);
}

public static function fromResponse(array $data): self
{
return new self(
result: $data['result'],
summary: $data['summary'],
result: $data['result'] ?? null,
summary: $data['summary'] ?? null,
);
}
}
11 changes: 11 additions & 0 deletions src/Dto/Monitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace OhDear\PhpSdk\Dto;

use OhDear\PhpSdk\Enums\CheckResult;

class Monitor
{
public function __construct(
Expand Down Expand Up @@ -40,6 +42,15 @@ public function __construct(
public ?string $updatedAt = null,
) {}

public function checkResult(): ?CheckResult
{
if ($this->summarizedCheckResult === null) {
return null;
}

return CheckResult::tryFrom($this->summarizedCheckResult);
}

public static function fromResponse(array $data): self
{
return new self(
Expand Down
38 changes: 38 additions & 0 deletions src/Enums/CheckResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace OhDear\PhpSdk\Enums;

enum CheckResult: string
{
case Pending = 'pending';
case Succeeded = 'succeeded';
case Warning = 'warning';
case Failed = 'failed';
case ErroredOrTimedOut = 'errored-or-timed-out';

public function isUp(): bool
{
return match ($this) {
self::Succeeded, self::Warning => true,
default => false,
};
}

public function isDown(): bool
{
return match ($this) {
self::Failed, self::ErroredOrTimedOut => true,
default => false,
};
}

public function isPending(): bool
{
return $this === self::Pending;
}

public function isWarning(): bool
{
return $this === self::Warning;
}
}
21 changes: 21 additions & 0 deletions tests/Fixtures/Saloon/check-summary-warning.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"statusCode": 200,
"headers": {
"Date": "Sun, 10 Aug 2025 18:46:17 GMT",
"Content-Type": "application\/json",
"Transfer-Encoding": "chunked",
"Connection": "keep-alive",
"Server": "cloudflare",
"Vary": "Accept-Encoding",
"Cache-Control": "no-cache, private",
"X-Ratelimit-Limit": "500",
"X-Ratelimit-Remaining": "498",
"Access-Control-Allow-Origin": "*",
"X-Frame-Options": "SAMEORIGIN",
"X-Xss-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
"Cf-Cache-Status": "BYPASS"
},
"data": "{\"result\":\"warning\",\"summary\":\"Primary location reports down, secondary confirms reachable.\"}",
"context": []
}
57 changes: 57 additions & 0 deletions tests/OhDearTests/CheckResultTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

use OhDear\PhpSdk\Enums\CheckResult;

it('has all expected cases', function () {
expect(CheckResult::cases())->toHaveCount(5);
expect(CheckResult::Pending->value)->toBe('pending');
expect(CheckResult::Succeeded->value)->toBe('succeeded');
expect(CheckResult::Warning->value)->toBe('warning');
expect(CheckResult::Failed->value)->toBe('failed');
expect(CheckResult::ErroredOrTimedOut->value)->toBe('errored-or-timed-out');
});

it('can be created from string values', function () {
expect(CheckResult::from('succeeded'))->toBe(CheckResult::Succeeded);
expect(CheckResult::from('warning'))->toBe(CheckResult::Warning);
expect(CheckResult::from('failed'))->toBe(CheckResult::Failed);
expect(CheckResult::from('pending'))->toBe(CheckResult::Pending);
expect(CheckResult::from('errored-or-timed-out'))->toBe(CheckResult::ErroredOrTimedOut);
});

it('returns null for unknown values with tryFrom', function () {
expect(CheckResult::tryFrom('unknown'))->toBeNull();
expect(CheckResult::tryFrom(''))->toBeNull();
});

it('correctly identifies up states', function () {
expect(CheckResult::Succeeded->isUp())->toBeTrue();
expect(CheckResult::Warning->isUp())->toBeTrue();
expect(CheckResult::Pending->isUp())->toBeFalse();
expect(CheckResult::Failed->isUp())->toBeFalse();
expect(CheckResult::ErroredOrTimedOut->isUp())->toBeFalse();
});

it('correctly identifies down states', function () {
expect(CheckResult::Failed->isDown())->toBeTrue();
expect(CheckResult::ErroredOrTimedOut->isDown())->toBeTrue();
expect(CheckResult::Succeeded->isDown())->toBeFalse();
expect(CheckResult::Warning->isDown())->toBeFalse();
expect(CheckResult::Pending->isDown())->toBeFalse();
});

it('correctly identifies pending state', function () {
expect(CheckResult::Pending->isPending())->toBeTrue();
expect(CheckResult::Succeeded->isPending())->toBeFalse();
expect(CheckResult::Warning->isPending())->toBeFalse();
expect(CheckResult::Failed->isPending())->toBeFalse();
expect(CheckResult::ErroredOrTimedOut->isPending())->toBeFalse();
});

it('correctly identifies warning state', function () {
expect(CheckResult::Warning->isWarning())->toBeTrue();
expect(CheckResult::Succeeded->isWarning())->toBeFalse();
expect(CheckResult::Failed->isWarning())->toBeFalse();
expect(CheckResult::Pending->isWarning())->toBeFalse();
expect(CheckResult::ErroredOrTimedOut->isWarning())->toBeFalse();
});
3 changes: 3 additions & 0 deletions tests/OhDearTests/ChecksTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use OhDear\PhpSdk\Enums\CheckResult;
use OhDear\PhpSdk\Requests\Checks\DisableCheckRequest;
use OhDear\PhpSdk\Requests\Checks\EnableCheckRequest;
use OhDear\PhpSdk\Requests\Checks\RequestCheckRunRequest;
Expand All @@ -21,6 +22,8 @@

expect($check->id)->toBe(940704);
expect($check->enabled)->toBe(true);
expect($check->latestRunResult)->toBe('succeeded');
expect($check->checkResult())->toBe(CheckResult::Succeeded);
});

it('can disable a check', function () {
Expand Down
21 changes: 21 additions & 0 deletions tests/OhDearTests/MonitorsTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use OhDear\PhpSdk\Enums\CheckResult;
use OhDear\PhpSdk\Enums\CheckType;
use OhDear\PhpSdk\Requests\Monitors\AddToBrokenLinksWhitelistRequest;
use OhDear\PhpSdk\Requests\Monitors\CreateMonitorRequest;
Expand Down Expand Up @@ -36,6 +37,8 @@
$monitor = $this->ohDear->monitor(82063);

expect($monitor->url)->toBe('https://laravel.com');
expect($monitor->summarizedCheckResult)->toBe('succeeded');
expect($monitor->checkResult())->toBe(CheckResult::Succeeded);
});

it('can create a monitor', function () {
Expand Down Expand Up @@ -74,6 +77,24 @@
$checkSummary = $this->ohDear->checkSummary(82060, CheckType::CertificateHealth);

expect($checkSummary->result)->toBe('succeeded');
expect($checkSummary->checkResult())->toBe(CheckResult::Succeeded);
expect($checkSummary->checkResult()->isUp())->toBeTrue();
expect($checkSummary->checkResult()->isDown())->toBeFalse();
expect($checkSummary->checkResult()->isWarning())->toBeFalse();
});

it('can get a warning check summary for a monitor', function () {
MockClient::global([
GetCheckSummaryRequest::class => MockResponse::fixture('check-summary-warning'),
]);

$checkSummary = $this->ohDear->checkSummary(82060, CheckType::Uptime);

expect($checkSummary->result)->toBe('warning');
expect($checkSummary->checkResult())->toBe(CheckResult::Warning);
expect($checkSummary->checkResult()->isUp())->toBeTrue();
expect($checkSummary->checkResult()->isDown())->toBeFalse();
expect($checkSummary->checkResult()->isWarning())->toBeTrue();
});

it('can get notification destinations for a monitor', function () {
Expand Down
Loading