alibabacloud/openapi-core
The core runtime for Alibaba Cloud PHP SDK clients, handling request signing, credential and endpoint resolution, retries, timeouts, error parsing, and gateway communication so generated OpenAPI clients can call Alibaba Cloud services reliably.
Understand what you're installing. alibabacloud/openapi-core is not a product SDK. It is the framework-agnostic runtime layer that every generated Alibaba Cloud PHP SDK sits on top of (alibabacloud/ecs-20140526, alibabacloud/oss-*, alibabacloud/slb-*, etc.). Its classes live under AlibabaCloud\SDK\OpenapiCore\, and it pulls in alibabacloud/darabonba-openapi, which is where the day-to-day models you actually type (Config, Params, RuntimeOptions) come from — namespace Darabonba\OpenApi\Models\.
That explains the "0 dependents": Composer dependency counts only see this package, not the several hundred product SDKs that require it transitively. You will almost never composer require alibabacloud/openapi-core directly. You get it as a side effect of installing a product SDK, or you install it deliberately when you need the generic gateway API (a product Alibaba Cloud does not publish an SDK for, or an older API version).
Where to look first. Within vendor/alibabacloud/openapi-core/src, open AlibabaCloud.php (the static entry point), OpenApiClient.php (the instance entry point), and Models/Params.php + Models/RuntimeOptions.php — those two models define almost every knob you will ever touch. Then read the README of the product SDK you installed; that is where action, version, style, and bodyType values for your specific API come from.
Minimal setup in a Laravel app.
composer require alibabacloud/openapi-core
// config/services.php
'alibaba_cloud' => [
'access_key_id' => env('ALIBABA_CLOUD_ACCESS_KEY_ID'),
'access_key_secret' => env('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
'region' => env('ALIBABA_CLOUD_REGION', 'cn-hangzhou'),
],
First use case: the generic call, for an API with no published SDK.
use AlibabaCloud\SDK\OpenapiCore\AlibabaCloud;
use Darabonba\OpenApi\Models\Config;
use Darabonba\OpenApi\Models\Params;
use Darabonba\OpenApi\Models\RuntimeOptions;
$config = new Config([
'accessKeyId' => config('services.alibaba_cloud.access_key_id'),
'accessKeySecret' => config('services.alibaba_cloud.access_key_secret'),
'endpoint' => 'ecs.cn-hangzhou.aliyuncs.com',
]);
$params = new Params([
'action' => 'DescribeInstances',
'version' => '2014-05-26',
'protocol' => 'HTTPS',
'pathname' => '/',
'method' => 'POST',
'authType' => 'AK',
'style' => 'RPC', // RPC | ROA
'reqBodyType' => 'formData', // formData | json | byte | stream
'bodyType' => 'json', // json | byte | stream | string
]);
$result = AlibabaCloud::callApi($params, $config, new RuntimeOptions([
'connectTimeout' => 3000,
'readTimeout' => 10000,
]));
// $result is a Tea model: $result->body, $result->headers, $result->statusCode
If you have a product SDK, skip all of the above and use its typed client — new Ecs(new Config([...])), then $client->describeInstances($request). Same underlying package, far better DX.
Bind one client as a singleton, not per-controller. Config and the client object are cheap but the underlying Guzzle handler stack and credential provider chain are not free. Register a scoped/singleton binding in a service provider and inject an interface you own:
// app/Providers/AlibabaCloudServiceProvider.php
$this->app->singleton(CloudClient::class, function () {
$config = new Config([
'accessKeyId' => config('services.alibaba_cloud.access_key_id'),
'accessKeySecret' => config('services.alibaba_cloud.access_key_secret'),
'endpoint' => 'ecs.'.config('services.alibaba_cloud.region').'.aliyuncs.com',
'connectTimeout' => 3000,
'readTimeout' => 10000,
]);
return new CloudClient(new Ecs($config));
});
Wrap it behind your own interface. You want interface CloudClient { public function listInstances(): array; } with an implementation that calls AlibabaCloud::callApi(). This is the single most valuable pattern here: it gives you something to fake in tests, it lets you swap the raw SDK response for a DTO, and it stops Tea\Exception\TeaError leaking into your HTTP layer.
Pick a credential strategy explicitly, per environment.
| Environment | Credential |
|---|---|
| Local / CI | Access key from .env |
| ECS / ACK | ECS RAM role (EcsRamRoleCredentialProvider) — no secrets at all |
| Cross-account | RamRoleArnCredentialProvider (STS AssumeRole) |
| Customer-facing | BearerTokenCredentialProvider for OAuth-protected endpoints |
For anything running on Alibaba Cloud infrastructure, prefer the RAM role and set Config.credential rather than passing keys. That removes the secret from your .env, your config cache, and your logs.
Use RuntimeOptions for retries, not Config. Auto-retry belongs on the call, so you can apply a different policy to a read (Describe*, retry freely) than to a write (Create*, retry with idempotency tokens only):
$runtime = new RuntimeOptions([
'autoretry' => true,
'maxAttempts' => 3,
'connectTimeout' => 2000,
'readTimeout' => 15000,
'proxy' => config('services.alibaba_cloud.proxy'), // null in prod
]);
Push calls into queued jobs. These are 100ms–3s network round trips to a region that may be far away. A request–response cycle is the wrong place for RunInstances. Encode the intent in the job, let the job own the retry policy, and expose status through your own model.
Log at the boundary, once. Because the SDK does its own signing and body serialization, debugging is much easier if you capture $params->action, the endpoint, the requestId from the error body, and the HTTP status in one structured log line. Do not log Config — it contains the access key secret in plaintext.
For multi-region work, create one client per region and key them in a map. Config.endpoint is fixed at construction; there is no per-call region override.
Config silently ignores unknown keys. A typo like 'access_key_id' instead of 'accessKeyId' produces an empty credential and a confusing InvalidAccessKeyId.NotFound at call time rather than a config error. Validate the four required keys at boot; do not trust the constructor.connectTimeout and readTimeout explicitly. This is the single most common cause of hung queue workers with this package.Tea\Exception\TeaError (inspect $e->code, $e->data['RequestId'] for support tickets). Transport failures bubble out as GuzzleHttp\Exception\ConnectException / RequestException. Catch \Throwable at your wrapper boundary and normalize.Throttling and ServiceUnavailable are expected, not exceptional. Alibaba Cloud rate-limits per API action. Honor the Retry-After-ish semantics with exponential backoff; do not let autoretry be your only defence on high-volume jobs.reqBodyType / bodyType / style must match the API's documentation exactly. Getting style: RPC on an ROA API, or bodyType: json where byte is required, yields IncompleteSignature or InvalidParameter that looks like a credentials problem but isn't.guzzlehttp/psr7 — it is the usual conflict when two vendor SDKs disagree."alibabacloud/openapi-core": "~0.x.0" inside a lockfile you commit is fine; a floating * is not, because a generator change can alter Params defaults under you.Http::fake() equivalent — the client builds its own handler stack. Faking means either (a) mocking your own interface, which is why the wrapper pattern matters, or (b) injecting a Guzzle MockHandler through RuntimeOptions['handler'] if that release supports it. Option (a) is the only one I would rely on.SecurityTokenExpired after a worker has been up for hours, you are holding a client past its token refresh window — resolve it scoped per request instead of singleton.ignoreSSL and proxy are your local-dev escape hatches. Reach for them only behind an explicit app()->isLocal() guard; they exist for debugging through Charles/mitmproxy and nothing else.*Request classes are generated and regenerated. If you need extra behaviour, subclass your own DTO around the response rather than modifying anything in vendor/.How can I help you explore Laravel packages today?