alibabacloud/tea
Tea is the core PHP runtime underpinning Alibaba Cloud SDKs, providing request/response abstractions, HTTP transport, retries, and error handling. Use it as a dependency when building or customizing Alibaba Cloud API clients rather than for standalone features.
alibabacloud/tea is not a client library you call for a specific service — it's the runtime layer that every Alibaba Cloud "Darabonba/Tea"-generated SDK (alibabacloud/ecs-20140526, alibabacloud/oss-*, alibabacloud/dysmsapi-*, …) sits on top of. If you have any Alibaba SDK installed, you already have it.
Step 1 — confirm it's there and see who pulled it in
composer show alibabacloud/tea # version in your tree
composer why alibabacloud/tea # which SDK(s) require it
Installing it standalone is only useful when you want to hand-roll HTTP calls against an endpoint that has no generated SDK.
composer require alibabacloud/tea alibabacloud/tea-utils
Step 2 — read four files, ignore the rest
| File | Why you care |
|---|---|
vendor/alibabacloud/tea/src/Tea.php |
The static facade: request(), merge(), is4xx(), is5xx(), sleep(). The retry loop lives here. |
src/Request.php |
Mutable value object: protocol, method, pathname, headers, query, body, port. Everything you can influence goes through this. |
src/Response.php |
getStatusCode(), getHeaders(), getBody(), getStatusMessage(). |
src/Exception/TeaError.php, TeaUnableRetryError.php |
The two exceptions you will actually catch. TeaError carries a data array, not just a message. |
Note the namespace is AlibabaCloud\Tea, not Tea — use Tea\Tea; will fail to autoload.
Step 3 — first use case: fire a request by hand
The minimum viable call is building a Request and handing it to Tea::request().
use AlibabaCloud\Tea\Request;
use AlibabaCloud\Tea\Tea;
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions; // from alibabacloud/tea-utils
$request = new Request();
$request->setProtocol('https');
$request->setMethod('GET');
$request->setPathname('/v1/status');
$request->setHeaders([
'host' => 'api.example.com', // required: curl routes on this, not on the URL
'accept' => 'application/json',
]);
$request->setQuery(['page' => '1']);
$request->setBody(null);
$runtime = new RuntimeOptions(['connectTimeout' => 3000, 'readTimeout' => 5000]);
$response = Tea::request($request, $runtime);
$payload = json_decode($response->getBody(), true);
If that works, you understand 80% of the package. Everything else in the ecosystem is generated code that builds exactly this structure.
Method names have shifted across
0.1.x → 0.2.xreleases. When in doubt,grep -n 'function ' vendor/alibabacloud/tea/src/Tea.phpbefore writing against it — faster than reading the docs, which are essentially nonexistent.
Pattern A — the 95% case: never touch the core directly. Generated clients already wrap Tea::request() with signing, endpoint resolution, and retry. Your job is only to supply RuntimeOptions and read $response->body:
$runtime = new RuntimeOptions([
'autoretry' => true,
'maxAttempts' => 3,
'connectTimeout' => 5000,
'readTimeout' => 10000,
]);
$response = $client->describeInstancesWithOptions($request, $runtime);
Reach for the core only when a generated SDK's defaults are wrong.
Pattern B — a custom client for a non-Alibaba API. The real value of this package outside Alibaba is that Request/Response are plain, serializable objects. Use them as your transport abstraction so the rest of your code is transport-agnostic:
final class MyApiDriver
{
public function __construct(private RuntimeOptions $runtime) {}
public function call(string $method, string $path, array $query = [], $body = null): array
{
$request = new Request();
$request->setProtocol('https');
$request->setMethod($method);
$request->setPathname($path);
$request->setHeaders(['host' => 'api.example.com', 'content-type' => 'application/json']);
$request->setQuery($query);
$request->setBody($body === null ? null : json_encode($body));
return json_decode(Tea::request($request, $this->runtime)->getBody(), true) ?? [];
}
}
Pattern C — configuration layering with Tea::merge(). Generated clients merge their construction-time config with per-call options via Tea::merge($a, $b). If you write a wrapper client, use the same idiom so callers can override timeouts, endpoint, or protocol per request without you writing a withX() for each field.
Pattern D — let the runtime retry, don't hand-roll it. RuntimeOptions::$autoretry + maxAttempts drives an exponential-backoff loop inside Tea::request(). Wrapping that in your own for ($i = 0; $i < 3; $i++) produces multiplicative retries (9 attempts from 3×3) and duplicate side effects on non-idempotent POSTs. Set autoretry only on idempotent calls.
Pattern E — structured error handling. Parse TeaError for the fields you actually need for logs and support tickets:
try {
$response = $client->sendMessageWithOptions($request, $runtime);
} catch (TeaError $e) {
$data = $e->getData(); // ['code'=>..., 'message'=>..., 'requestId'=>..., 'httpCode'=>...]
Log::warning('aliyun api error', $data + ['operation' => 'SendMessage']);
throw new ApiException($data['message'] ?? $e->getMessage(), $data['httpCode'] ?? 0);
} catch (TeaUnableRetryError $e) {
// all attempts exhausted — the useful detail is on the inner exception
report($e->getLastException() ?? $e);
}
Pattern F — test request construction without the network. Because Request is just a settable object, assert on getPathname(), getQuery(), getHeaders() and getBody() directly. Only integration-test against a real endpoint. This is the cheapest test layer available in the whole Alibaba SDK stack.
Pattern G — cross-cutting headers. Correlation IDs, tenant tags, and proxy headers can be injected onto $request->getHeaders() before handing it to Tea::request(). This works because signing happens upstream in the SDK's doRequest(). See the pitfall below.
Never mutate headers after signing. Alibaba requests are signed over the canonicalized header set (x-acs-*, host, content-type). If you inject a header after the SDK has signed, you get a SignatureDoesNotMatch that looks like a credential problem but isn't. Inject before signing, or restrict yourself to unsigned headers.
host header is mandatory on hand-built requests. Tea::request() goes through curl and routes on the header, not on a parsed URL — omit it and you get a confusing DNS/connection error.
The license is NOASSERTION on Packagist. Read vendor/alibabacloud/tea/LICENSE before shipping this in a distributed product. It's almost certainly Apache-2.0 like the rest of the ecosystem, but do the two-minute check rather than guessing from the metadata.
"Dependents: 0" is a Packagist artifact, not reality. Hundreds of alibabacloud/* packages require it transitively. Judge the package by the generated SDKs consuming it, not by its own star count — 7 stars is simply because nobody requires the transport layer on purpose.
Stale-looking releases are normal here. Last release 2023-05-16 with a 5.6-era PHP constraint in composer.json means it installs on almost anything — that is not evidence it's tested on PHP 8.3. Run your integration tests on your actual target version, especially around curl handle cleanup and byte-string handling.
Composer conflict magnet. Different generated SDKs pin alibabacloud/tea to different ~0.x minors. When composer require explodes, composer why alibabacloud/tea and composer update --with-dependencies alibabacloud/tea usually resolves it; downgrading the newest SDK is the fallback.
Tea::sleep() takes milliseconds. The rest of PHP sleeps in seconds. A Tea::sleep(5) "delay" is a 5 ms no-op.
Tea::is4xx() / Tea::is5xx() take a Response, not an int. Passing $response->getStatusCode() returns a truthy garbage result rather than erroring.
getBody() is not guaranteed to be a clean JSON string. Error responses may be empty, XML, or an HTML gateway page. Always guard: $data = json_decode($response->getBody() ?? '', true); if (!is_array($data)) { ... }.
Header arrays are case-sensitive in PHP, even though HTTP isn't. Reading $response->getHeaders()['content-type'] when the wire sent Content-Type yields null. Look up case-insensitively in any wrapper you write.
ignoreSSL exists but should never reach production. It's for local proxies and self-signed test endpoints only. Lint for it in review.
Console::log() writes to stdout/STDERR. Fine in a CLI worker, silently corrupts JSON responses in FPM. Route logs through your framework's logger instead.
TeaUnableRetryError is a wrapper. Its message is generic ("Retry failed"); the actionable exception and response are on the inner accessors. Catching it and logging getMessage() alone makes retry failures undebuggable.
Extension point worth knowing: the retry/backoff policy is entirely driven by the runtime bag you pass in — core doesn't hold global state. That means you can safely run two clients with different timeout/retry profiles in the same process, which is the cleanest way to isolate a flaky dependency.
How can I help you explore Laravel packages today?