ruwler/ruwler-sdk-php
A PHP SDK for integrating the Ruwler API into your Laravel or PHP applications, providing a lightweight client for making requests and handling responses. Suitable for developers who need a simple, framework-friendly wrapper to connect their projects to Ruwler services.
Reality check first. ruwler/ruwler-sdk-php has zero stars and zero dependents, which means nobody has stress-tested it in production and there is no community paper trail. Treat the onboarding as a code review, not a composer require and move on. Budget an hour to read the source before you let it into a Laravel app.
Day-one inspection order:
composer.json in the repo — check require constraints (PHP ^7.4 vs ^8.1, Guzzle version, PSR-18/PSR-17 compliance). A stale Guzzle pin will fight your app's dependency resolution.README.md — look for a vendor/bin example, a base URL, and an auth story. If any of the three is missing, the SDK is incomplete.src/ — find the entry class (usually Ruwler, Client, or Api). Count the public methods; that's the entire surface area you can use.tests/ — if empty or absent, you now own the test suite. Plan accordingly.LICENSE and last commit date — abandoned packages are a real risk at this star count.Install:
composer require ruwler/ruwler-sdk-php
If it fails with a conflict, you're probably on a Guzzle/PSR major mismatch. Don't --ignore-platform-reqs your way past it; that just relocates the break.
First use case — a smoke call, in tinker:
$client = new \Ruwler\Client(config('services.ruwler.key'));
dump($client-><firstReadMethod>());
Do this before writing any Laravel glue. Confirm the request actually reaches Ruwler, that errors throw (rather than returning null), and what the raw response shape looks like. Save one real response payload as a fixture — you'll need it for fakes later.
Credentials go in config/services.php, sourced from .env. Never env() inside the SDK wrapper; config caching will silently empty it.
// config/services.php
'ruwler' => [
'key' => env('RUWLER_API_KEY'),
'base_url' => env('RUWLER_BASE_URL', 'https://api.ruwler.example'),
'timeout' => env('RUWLER_TIMEOUT', 10),
],
Wrap it. Always. Do not let Ruwler\Client leak into controllers and jobs. The package is unproven and unversioned-in-practice; a single wrapper class is your escape hatch when you have to fork or replace it.
namespace App\Services;
use Ruwler\Client;
class RuwlerService
{
public function __construct(private Client $client) {}
public function lookup(string $id): RuwlerResult
{
$raw = $this->client-><method>($id);
return RuwlerResult::fromArray($raw); // your DTO, not the SDK's
}
}
Bind once in a service provider. The package almost certainly has no Laravel auto-discovery (small SDKs rarely ship one), so you register it yourself:
// AppServiceProvider::register()
$this->app->singleton(Client::class, fn () => new Client(
config('services.ruwler.key'),
config('services.ruwler.base_url'),
));
$this->app->alias(Client::class, 'ruwler');
Singleton matters if the SDK holds a Guzzle instance — you want connection reuse, not a new TLS handshake per resolution.
DTO boundary. Map SDK responses into App\Data\... objects at the edge. Two payoffs: you can fake the service cleanly in tests, and when the SDK's array shape changes (it will), you patch one fromArray() instead of every callsite.
Reads are sync, writes go on the queue. Reads through the SDK inline are fine. Anything that creates/mutates remote state belongs in a job with $tries = 3, backoff(), and a failed() handler that alerts. The SDK will not give you retries for free.
class PushRecordToRuwler implements ShouldQueue
{
public function __construct(public readonly int $recordId) {}
public function handle(RuwlerService $ruwler): void { /* ... */ }
}
Faking in tests. Rather than mocking the vendor class, fake your wrapper or bind a fake Client:
$this->mock(RuwlerService::class, fn ($m) => $m
->shouldReceive('lookup')->andReturn(new RuwlerResult(...)));
If you must test at the HTTP layer, and the SDK uses Guzzle directly, Http::fake() will not intercept it — you'd need Guzzle's MockHandler. This is a strong argument for the wrapper pattern.
Integration tip: if you're exposing this via your own API, cache read responses (Cache::remember(..., 60)) at the wrapper layer. Third-party SDK rate limits are the usual first production incident.
php artisan vendor:publish to do something.get_class($e). If the SDK throws a bare \Exception on network failure and returns false on 4xx (both happen in hand-rolled SDKs), you'll write defensive checks around every call. Normalize into your own exception type at the wrapper boundary.env() outside config. If the SDK reads getenv() internally, config:cache won't save you — and config:cache in staging will produce confusing "works locally, dead in prod" bugs. Verify by running php artisan config:cache early.guzzlehttp/guzzle:^6 constraint will block Laravel 10/11 apps (which want ^7). Check with composer why-not guzzlehttp/guzzle. Forking to bump the constraint is usually a two-line composer.json edit, not a rewrite.setClient(). Default Guzzle timeouts (0 = infinite) will hang your workers.Middleware::tap or HandlerStack logger to a raw request/response dump during integration. Undocumented SDKs hide payload-shape surprises, and a 200 with {"error": ...} in the body is a classic."ruwler/ruwler-sdk-php": "1.0.*" — not ^1.0, not dev-main. At zero stars there is no bus factor protection against a force-push or a breaking 1.1.app/Support/Ruwler/, or budget for an HTTP client replacement when it rots. Don't let it become load-bearing without that plan.GuzzleHttp\ClientInterface or HandlerStack? That's your only clean hook for retries, metrics, and tracing. If it hardcodes new GuzzleHttp\Client() internally, you're limited to wrapping.readonly, enums, or named arguments. If your app targets 7.4 or 8.0, check composer.json require.php before installing or you'll get a parse error at autoload time — not a clean composer error.How can I help you explore Laravel packages today?