wxm/sdk-kernel
wxm/sdk-kernel is a lightweight foundation for building Laravel and PHP SDKs, offering shared contracts, configuration, service-provider bootstrapping, and a thin HTTP layer so client packages stay consistent, testable, and easy to integrate.
composer require wxm/sdk-kernel
Start by reading four files in src/, in this order:
Client.php — the abstract base you extend; holds config, service resolution, and the magic accessors.Config.php — the array-backed config object with accessor semantics.ServiceProvider.php — the contract for registering a service against a client.Exceptions/ — the failure modes the kernel throws (InvalidConfigException and friends).Then look at a real downstream consumer (e.g. wxm/wechat-sdk) — that is the intended shape of the package and the fastest way to see the kernel in situ.
Minimal first use case — wrap a small HTTP API:
use Wxm\SdkKernel\Client;
class AcmeClient extends Client
{
protected $providers = [
'user' => \Acme\Providers\UserProvider::class,
'invoice' => \Acme\Providers\InvoiceProvider::class,
];
}
$client = new AcmeClient([
'api_key' => getenv('ACME_KEY'),
'base_uri' => 'https://api.acme.test',
]);
$client->user->find(42); // lazily resolves the user service
$client->invoice->list(); // lazily resolves the invoice service
The kernel is deliberately thin: it gives you a config container, lazy service resolution and a uniform client->service->method() surface. You bring the HTTP transport and the service classes.
Provider-per-resource. Map a short name to a provider class in $providers. Every endpoint group (users, orders, webhooks) gets its own service class and its own provider, so the client stays a registry rather than a god object:
protected $providers = [
'user' => UserProvider::class,
'order' => OrderProvider::class,
'webhook' => WebhookProvider::class,
];
Constructor override for defaults. Merge caller config over your defaults before handing it to the parent, so incomplete new AcmeClient([]) still works against a sane base URI:
public function __construct(array $config = [])
{
parent::__construct(array_merge([
'base_uri' => 'https://api.acme.test',
'timeout' => 10,
], $config));
}
Config as the seam. Never read getenv() or globals inside a service; read $this->client->getConfig('api_key') instead. That keeps every service testable by constructing a client with a fixture array.
Thin services, fat providers. Put wiring (transport, auth headers, retry policy) in the provider and keep the service a list of endpoint methods that take scalars/arrays and return arrays. This is what makes the kernel pleasant day-to-day: $client->order->cancel($id) reads like the API doc.
Integration with Laravel. Register the client as a singleton in a service provider and publish a config file:
$this->app->singleton(AcmeClient::class, fn ($app) =>
new AcmeClient($app['config']->get('services.acme'))
);
Resolve by type-hint in controllers/jobs. Do not new the client per request — you lose the shared service instances and re-run config merging each time.
Testing. Because providers are just classes, swap the transport in the config array ('http_client' => $mockPsr18Client) and assert against the kernel-level API rather than against Guzzle internals. For pure unit tests, subclass the client with a stub provider that returns fixed payloads.
Multi-tenant / multi-credential. Instantiate more than one client — one per tenant — rather than mutating config at runtime. The kernel does not reload config on setConfig, and providers may have cached derived state.
wxm/* SDK family. Pin an exact version in composer.json and vendor-lock; do not float ^1.0 on a package that can rewrite a method signature in a patch release.$providers (or in $client->usr->...) fails at call time, not at boot. Add a smoke test that touches every registered service so a broken provider is caught in CI, not in production.$client->user calls return the same object. Any state you stash on a service (last request id, pagination cursor) leaks across calls. Keep services stateless or expose an explicit reset().__call/__get mean PHPStan and IDEs cannot see $client->user->find(). Mitigate with @property docblocks on the client class, or add explicit typed getters for the services you call most.Config is array-shaped, so nested keys bite. Decide early whether you support dot notation and use it consistently; mixing config('a.b') and config('a')['b'] across services is the most common source of silent nulls.Wxm\SdkKernel\.array_keys($client->getProviders()) and the resolved class name before the throw — nine times out of ten it is a namespace mismatch from PSR-4 autoloading (src/Providers vs src/ServiceProvider).composer.json require block before planning your own PHP floor.How can I help you explore Laravel packages today?