Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Sdk Laravel Package

juststeveking/php-sdk

A framework-agnostic PHP SDK that provides a clean, lightweight foundation for building and consuming API clients. It simplifies request handling and response structure for PHP and Laravel projects, making integrations easier to build and reuse.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin immediately.

composer require juststeveking/php-sdk:3.0.0

Pin the exact version. The 3.0.0 release notes are a single line ("Feature/new release") with one PR reference and no upgrade guide, and the package has zero dependents — no one has absorbed the breaking changes ahead of you. Lock it, then read vendor/juststeveking/php-sdk/src in full. The package is small enough that an hour of source reading replaces a week of guessing at conventions.

Read in this order before writing a line of code:

  1. The client/connector base class. See how base URL, headers, and the PSR-18 HTTP client are injected. This is where you'll subclass.
  2. The request base class. Understand how method, path, payload, and headers get assembled — this defines what each endpoint class looks like.
  3. The response base class. Critically: check whether the body is exposed as a consumable PSR-7 stream or a cached string. This determines how you hydrate DTOs.
  4. Any DTO/collection helper. There usually isn't much here; assume you're writing your own hydration.

First use case: one authenticated GET, end to end. Don't scaffold your whole API surface. Pick a single read endpoint, build one client subclass, one request class, and one typed return. That path touches config, transport, request assembly, response parsing, and error mapping — every layer where the abstraction can bite. Friction shows up on endpoint one, not endpoint fifteen.

Because the package is deliberately framework-agnostic, there is no service provider, no facade, and no config file. You own the Laravel wiring:

// config/services.php
'acme' => [
    'base_url' => env('ACME_BASE_URL'),
    'token'    => env('ACME_TOKEN'),
    'timeout'  => 10,
],

// AppServiceProvider::register()
$this->app->singleton(AcmeClient::class, fn () => new AcmeClient(
    http:    new \GuzzleHttp\Client(['timeout' => config('services.acme.timeout')]),
    baseUrl: rtrim(config('services.acme.base_url'), '/'),
    token:   config('services.acme.token'),
));

Bind the PSR-18 client explicitly. Relying on php-http/discovery is the most common first-hour friction: it silently picks a transport, or fails with an unhelpful "no PSR-18 client found" deep inside a request.

Implementation Patterns

One client subclass per remote API, many resource classes. The client owns transport concerns — base URL, auth header, timeout, Guzzle handler stack. Resources own endpoint knowledge and are constructed from the client. Keep the resource layer free of Http:: calls, config() lookups, and Eloquent. That's what makes it portable and testable.

Resources return DTOs, never arrays. An array leaking out of a resource becomes an implicit contract across your app, and every consumer re-guesses key names and nullability. Map at the boundary:

final class InvoiceResource
{
    public function __construct(private AcmeClient $client) {}

    public function find(string $id): Invoice
    {
        $response = $this->client->send(new GetInvoiceRequest($id));

        return Invoice::from($response->json());
    }

    /** @return \Generator<int, Invoice> */
    public function each(?string $cursor = null): \Generator
    {
        while ($cursor !== null) {
            $page = $this->client->send(new ListInvoicesRequest($cursor));
            foreach ($page->json('data') as $row) {
                yield Invoice::from($row);
            }
            $cursor = $page->json('next_cursor');
        }
    }
}

spatie/laravel-data earns its keep here; hand-written ::from() constructors are fine for a dozen endpoints and add zero dependencies.

Encapsulate every endpoint as a named request class. Even when the base request is fluent enough to build inline, a class gives you one place to later add an idempotency key, a per-endpoint timeout, or a signature — and makes the endpoint greppable from a stack trace. A stack trace showing Acme\Sdk\Requests\GetInvoiceRequest is worth ten showing send().

Model pagination as a generator. The package leaves pagination to you. A resource method returning \Generator composes beautifully with LazyCollection for bulk syncs and keeps memory flat:

LazyCollection::make(fn () => $client->invoices()->each())
    ->chunk(500)
    ->each(fn ($batch) => InvoiceRow::upsert($batch->all(), ['id']));

Centralise error mapping in the client — once. Map non-2xx responses to a small set of your own exceptions (RateLimited, NotFound, ValidationFailed) at the single point where the response comes back. Controllers and jobs then never see PSR-7 status codes, and if you ever swap the SDK out, the blast radius is one class.

Test at the transport boundary, not the HTTP facade. This is the biggest workflow change from normal Laravel work. Http::fake() has no effect — the SDK talks to its own PSR-18 client. Use GuzzleHttp\Handler\MockHandler or a hand-rolled fake ClientInterface returning canned PSR-7 responses, and bind it in the container during tests:

$this->app->instance(AcmeClient::class, new AcmeClient(
    http:    new Client(['handler' => HandlerStack::create(new MockHandler([$fakeResponse]))]),
    baseUrl: 'https://acme.test',
    token:   'test-token',
));

Build this harness in the first week. Without it every test either hits the real network or mocks your own SDK — and mocking your own SDK tests nothing.

Wire it into places Laravel developers actually invoke APIs:

  • Console commands and queued jobs for sync work — the resource layer is plain PHP, so it runs in a worker with no extra ceremony.
  • Form requests / action classes when an inbound request fans out to a third party; wrap the SDK call in an action so controllers stay oblivious.
  • A token-provider closure for OAuth refresh — inject the closure into the client rather than reading config() inside the request layer, so refresh mid-run works and tests can swap the provider.

Keep your public API free of SDK types. Subclass the request/response bases, but don't type-hint them in constructors your other code depends on. The seam you want is "my client interface," implemented by a class that happens to extend the SDK. That's what makes an eventual fork or replacement cheap.

Gotchas and Tips

Laravel's HTTP client is not involved, and that costs more than you expect. No Http::fake(), no ->retry(), no ->throw(), no Http::pool(), no request/response events, no Telescope capture. If the client you're building is internal-only and never published, Http:: is the better tool and this package is overhead. Adopt it when the artifact genuinely needs to run outside Laravel — Symfony, Slim, plain PHP — because that portability is the only thing you're buying.

Retries, backoff, rate limiting, and token refresh are yours. The scope discipline is a feature, but it's a feature you staff. Concretely:

  • Add a Guzzle RetryMiddleware on the handler stack for 429/5xx with jittered exponential backoff.
  • Honour Retry-After explicitly rather than relying on your backoff curve.
  • Treat idempotency keys as required on every POST/PUT you retry.
  • Handle token refresh in a provider closure, not inside the request layer.

Bolt these on during the spike, not after the SDK is in production — it's materially harder later.

Read PSR-7 response streams once. If the base response exposes the body as a stream, calling getContents() twice silently returns empty on the second read. Cache the decoded payload (or cast to string once) in a thin wrapper around the response before you build twenty resources on top of it.

Trailing slashes in the base URL are a real bug source. /v1 + /invoices vs /v1/ + /invoices produces //invoices, which some gateways 404 and others normalise. Normalise in the client constructor (rtrim($base, '/')) and never concatenate by hand in resources.

Don't map SDK exceptions straight to HTTP responses. A GuzzleHttp\Exception\ConnectException escaping to a controller becomes a 500 on a user-facing page. Catch at the client boundary, rethrow domain exceptions, and let Laravel's exception handler decide the status. Decide early whether a third-party timeout during a web request should degrade gracefully — usually yes.

Version treadmill: budget real time. 3.0.0 proves majors ship without migration notes. Each one is a source-reading exercise, not a composer update. Pin exactly, upgrade deliberately, and record diff findings in your own changelog. Half a day per major is the honest estimate.

Bus factor is 1 — plan the exit before you need it. MIT plus a few thousand lines means the escape hatches are cheap:

  • Keep your DTOs, exceptions, and resource layer free of SDK base classes where practical.
  • If you're publishing the resulting SDK to customers, consider vendoring or forking with a named internal owner.
  • Write the internal docs in the same sprint you build the SDK, not after — the package has no Stack Overflow presence and almost no engineers have seen the codebase. Onboarding cost is entirely yours.

Reconsider the whole thing if the surface grows. Two dozen stable endpoints with predictable payloads is the sweet spot. Throw in OAuth flows, webhooks with signature verification, streaming, or OpenAPI-driven contracts and the honest answer is Saloon (for HTTP concerns) or a generator (for spec fidelity), with your resource layer written in-house either way.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment