Exploring our new PHP SDK, built using Saloon

Published on August 25, 2025 by Freek Van der Herten

Today, next to [Ping and TCP monitoring](TODO add link), we've also launched a new PHP SDK package, which has been rebuilt from scratch using the wonderful Saloon library. Using our new SDK, you can easily use the entire Oh Dear API.

In this blog post, I'd like to show you how you can use the new SDK and how it works under the hood.

Using the SDK

With the SDK package installed (you'll only have to require it using composer require ohdearapp/ohdear-php-sdk), you'll be able to instantiate the Oh Dear class like this:

use OhDear\PhpSdk\OhDear;

$ohDear = new OhDear('your-api-token');

That API token can be created on the API tokens screen.

On that OhDear class you can use one of the many available API methods.

Here's how you would create a simple monitor.

$monitor = $ohDear->createMonitor([
    'url' => 'https://example.com',
    'type' => 'http',
    'team_id' => 1,
]);

echo $monitor->url; // returns https://example.com

Of course, we'll start performing all of our checks on a new monitor right away.

Here's how you would list all broken links we detect on a site.

$brokenLinks = $ohDear->brokenLinks($monitorId);

foreach ($brokenLinks as $brokenLink) {
    echo "Broken link: {$brokenLink->crawledUrl}";
    echo "Status: {$brokenLink->statusCode}";
    echo "Found on: {$brokenLink->foundOnUrl}";
    echo "Link text: {$brokenLink->linkText}";
    echo "Internal link: " . ($brokenLink->internal ? 'Yes' : 'No') . "";
}

Here's a cool tidbit, you can't know from looking at the code above: you don't need to care about pagination. The brokenLinks method (and all other methods in our SDK that return multiple results) doesn't return a regular array. Instead it returns an iterator.

When you loop over the iterator, we will automatically fetch more pages of results from our API. It's completely transparent. So no matter how many broken links we detect on your site, looping over the iterator returned by brokenLinks will handle them all.

Built on top of Saloon

This way of handling pagination (with an iterator) is powered by Saloon, which is a wonderful package to build modern PHP SDKs. I really had a fun time developing our SDK because Saloon streamlines everything so well.

At the heart of a Saloon powered SDK is Connector. This is the class that has all of the basic information of how to connect to an API. Here's the OhDear connector from our package (redacted for brevity).

<?php

namespace OhDear\PhpSdk;

use Saloon\Http\Connector;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Throwable;

class OhDear extends Connector implements HasPagination
{
    use AcceptsJson;
    use AlwaysThrowOnErrors;
   
    protected string $apiToken;

    protected string $baseUrl;

    protected int $timeoutInSeconds;

    public function __construct(
        string $apiToken,
        string $baseUrl = 'https://ohdear.app/api/',
        int $timeoutInSeconds = 10,
    ) {
        $this->apiToken = $apiToken;
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->timeoutInSeconds = $timeoutInSeconds;
    }

    public function resolveBaseUrl(): string
    {
        return $this->baseUrl;
    }

    protected function defaultAuth(): TokenAuthenticator
    {
        return new TokenAuthenticator($this->apiToken);
    }

    protected function defaultHeaders(): array
    {
        return [
            'Accept' => 'application/json',
            'Content-Type' => 'application/json',
        ];
    }

    protected function defaultConfig(): array
    {
        return [
            'timeout' => $this->timeoutInSeconds,
        ];
    }

    // other methods
}

For each different API request, you can create a class that extends Saloon\Http\Request. In the class, you can define the URL that should be called, and the parameters it needs. Here's the GetMonitorRequest that is used to retrieve a single monitor.

<?php

namespace OhDear\PhpSdk\Requests\Monitors;

use OhDear\PhpSdk\Dto\Monitor;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;

class GetMonitorRequest extends Request
{
    protected Method $method = Method::GET;

    public function __construct(
        protected int $monitorId
    ) {}

    public function resolveEndpoint(): string
    {
        return "/monitors/{$this->monitorId}";
    }

    public function createDtoFromResponse(Response $response): Monitor
    {
        return Monitor::fromResponse($response->json());
    }
}

That Monitor object is a DTO class that will transform the array response from our API to a real PHP object, which is nicer to handle.

<?php

namespace OhDear\PhpSdk\Dto;

class Monitor
{
    public function __construct(
        public int $id,
        public ?int $teamId,
        public string $url,
        
        // other properties omitted for brevity
        
    ) {}

    public static function fromResponse(array $data): self
    {
        return new self(
            id: $data['id'],
            teamId: $data['team_id'],
            url: $data['url'],
            
            // other properties omitted for brevity
        );
    }
}

Using the connector and the request, you can get results from the API like this.

use OhDear\PhpSdk\Requests\Monitors\GetMonitorsRequest;

$request = new GetMonitorsRequest();

// raw response from the Oh Dear API
$response = $ohDear->send($request);

$monitor = $response->dto();

By using the connector/request this way, you have full control to customize requests as you see fit (you could for example use Saloon's concurrency functionality this way.)

Now, to make it easier for the package user, I've added methods to the connector class, that wrap up those requests, so it becomes easier to use.

// on the OhDear connector class

public function monitor(int $monitorId): Monitor
{
    $request = new GetMonitorRequest($monitorId);

    return $this->send($request)->dto();
}

With this in place, users can just get a monitor like this.

$monitor = $ohDear->monitor(1);

echo $monitor->url;

Easy peasy!

One thing that I'd like to highlight about Saloon, is its amazing testing facilities. Using the fixture recorder, you can easily test endpoints of the real SDK.

Here is the test of that endpoint to get a single monitor.

it('can get a single monitor', function () {
    MockClient::global([
        GetMonitorRequest::class => MockResponse::fixture('monitor'),
    ]);

    $monitor = $this->ohDear->monitor(82063);

    expect($monitor->url)->toBe('https://laravel.com');
});

In the code above you can see that we use Saloon's MockResponse::fixture function. This works a bit like snapshot testing. The first time you run this test, it will call the actual API and store its response in a file in the Fixtures directory of the test suite. The second time this test is run, it will not call the actual API anymore, but use the response saved in the Fixtures directory. And of course, this is a much faster test.

Notice that the test above mentioned the actual id of the created monitor. I don't mind that, because this is from our staging environment, which will be cleaned up by the time you read this. If you have sensitive values returned by your API, Saloon has got your back too as it can redact fixtures.

In closing

When I decided to rebuild our SDK in a modern way, I dreaded it a bit, as it's always a bit of tedious work. But because of Saloon, I really enjoyed the process. After creating the base setup and a couple of requests, I also used AI to complete most of the endpoints. Because Saloon divides all concerns of an SDK so nicely, the AI didn't have any problems discovering the structure. Be sure to check out the source code our package, to learn more about Saloon can be used.

As a user of Oh Dear, I hope you'll enjoy using our new SDK. All the most important API methods are covered by our SDK. Missing a method? Feel free to open a PR, or just reach out to support and we'll add it!

Start using Oh Dear today!

  • Access to all features
  • Cancel anytime
  • No credit card required
  • First 30 days free

More updates

Want to get started? We offer a no-strings-attached 30 day trial. No credit card required.

Start monitoring

You're all set in
less than a minute!