tencentcloud/cvm
PHP client for Tencent Cloud CVM (Cloud Virtual Machine). Provides APIs to create, query, start, stop, and manage instances, images, disks, and related resources, with request signing and response handling for PHP applications.
tencentcloud/cvm is the auto-generated PHP client for Tencent Cloud's Cloud Virtual Machine (CVM) API. It is a read-only split of the tencentcloud/tencentcloud-sdk-php monorepo; it is not a Laravel package — no facade, no service provider, no config to publish. You will wire it yourself (which is fine, and takes ~20 minutes).
Install
composer require tencentcloud/cvm
That pulls tencentcloud/common, which contains Credential, ClientProfile, HttpProfile, and TencentCloudSDKException. Pin the minor version of common in composer.json — it occasionally breaks between releases.
Credentials — read from .env, never hardcode:
TENCENTCLOUD_SECRET_ID=AKID...
TENCENTCLOUD_SECRET_KEY=...
TENCENTCLOUD_CVM_REGION=ap-guangzhou
# optional, only for STS / assume-role temporary credentials
TENCENTCLOUD_TOKEN=
Smallest useful call (read-only smoke test):
use TencentCloud\Common\Credential;
use TencentCloud\Common\Profile\ClientProfile;
use TencentCloud\Common\Profile\HttpProfile;
use TencentCloud\Cvm\V20170312\CvmClient;
use TencentCloud\Cvm\V20170312\Models\DescribeInstancesRequest;
$http = new HttpProfile();
$http->setEndpoint('cvm.tencentcloudapi.com');
$http->setProtocol('https');
$http->setConnTimeout(10);
$http->setReqTimeout(60);
$profile = new ClientProfile();
$profile->setHttpProfile($http);
$profile->setSignMethod('TC3-HMAC-SHA256');
$client = new CvmClient(
new Credential(config('services.tencent_cvm.secret_id'), config('services.tencent_cvm.secret_key')),
config('services.tencent_cvm.region'),
$profile
);
$req = new DescribeInstancesRequest();
$req->setLimit(20);
$req->setOffset(0);
$resp = $client->DescribeInstances($req);
foreach ($resp->getInstanceSet() as $instance) {
dump($instance->getInstanceId(), $instance->getInstanceState()); // RUNNING, STOPPED, ...
}
Where to look first
vendor/tencentcloud/cvm/src/TencentCloud/Cvm/V20170312/CvmClient.php — the authoritative list of actions this package supports. Every public function Xxx(XxxRequest $req) is an API call; there is no hidden surface.vendor/tencentcloud/cvm/src/TencentCloud/Cvm/V20170312/Models/ — one class per action request and per nested response type. The property names map 1:1 to the public API docs at cloud.tencent.com/document/api/213, so you can read the docs and translate directly.First real use case: an inventory view. Call DescribeInstances per region, filter on tags you control (tag:env, tag:owner), and cache the result for 60s. It's a free, read-only call and instantly proves credentials, region, and IAM policy are all correct.
CVM resources are region-scoped, so most real code needs more than one client. Wrap construction and cache by region:
// app/Services/Tencent/CvmManager.php
class CvmManager
{
private array $clients = [];
public function client(?string $region = null): CvmClient
{
$region ??= config('services.tencent_cvm.region');
return $this->clients[$region] ??= $this->make($region);
}
private function make(string $region): CvmClient
{
$cred = new Credential(
config('services.tencent_cvm.secret_id'),
config('services.tencent_cvm.secret_key'),
config('services.tencent_cvm.token'),
);
$http = new HttpProfile();
$http->setEndpoint('cvm.tencentcloudapi.com');
$http->setProtocol('https');
$http->setConnTimeout(10);
$http->setReqTimeout(60);
$profile = new ClientProfile();
$profile->setHttpProfile($http);
return new CvmClient($cred, $region, $profile);
}
}
Bind CvmManager as a singleton in AppServiceProvider. Do not bind CvmClient as a singleton — it carries a region.
// A. Setters (type-safe, IDE-friendly)
$req = new DescribeInstancesRequest();
$req->setInstanceIds(['ins-abc123']);
$req->setLimit(50);
// B. Raw API-shaped payload (great for passthrough, config-driven filters)
$req = DescribeInstancesRequest::fromJsonString(json_encode([
'Filters' => [['Name' => 'tag:env', 'Values' => ['prod']]],
'Limit' => 50,
'Offset' => 0,
]));
Use (A) inside your domain layer and (B) only when forwarding user-shaped filters from a UI.
DescribeInstances filters are an array of {Name, Values} objects. Common names: instance-id, instance-name, instance-state, zone, vpc-id, and tag:<key>.
$req = new DescribeInstancesRequest();
$req->setFilters([
['Name' => 'instance-state', 'Values' => ['RUNNING']],
['Name' => 'tag:env', 'Values' => ['production']],
]);
$req->setLimit(100);
$req->setOffset(0);
$resp = $client->DescribeInstances($req);
$total = $resp->getTotalCount(); // total matching, not page size
$page = $resp->getInstanceSet(); // array of Instance objects, possibly empty
Always loop until offset + count($page) >= total, and cap the loop — accounts with pathological churn can shift the total mid-pagination.
RunInstances is asynchronous. It returns InstanceIdSet immediately, but the instances exist in PENDING before reaching RUNNING. Do not assume success from a 200.
$req = RunInstancesRequest::fromJsonString(json_encode([
'ImageId' => 'img-xxxxxxxx',
'InstanceChargeType' => 'POSTPAID_BY_HOUR',
'InstanceType' => 'S5.MEDIUM4',
'Placement' => ['Zone' => 'ap-guangzhou-3'],
'InstanceCount' => 1,
'ClientToken' => $jobUuid, // idempotency key
'TagSpecification' => [[
'ResourceType' => 'instance',
'Tags' => [['Key' => 'env', 'Value' => 'staging']],
]],
]));
$ids = $client->RunInstances($req)->getInstanceIdSet();
Then a DescribeInstances poll with instance-id + instance-state filters, backing off 2s → 5s → 10s, with a hard timeout (~5 min, longer for GPU/bare-metal SKUs). Terminal failure states: LAUNCH_FAILED, TERMINATING.
ClientToken is the single most important field here: if a job retries after a network blip, the same token returns the original instance IDs instead of double-billing you.
$req = new StopInstancesRequest();
$req->setInstanceIds(['ins-abc123']);
$req->setStopType('SOFT'); // SOFT = graceful, HARD = pull the plug
$req->setStoppedMode('STOP_CHARGING'); // or KEEP_CHARGING if you need the IP preserved
Same shape for StartInstances, RebootInstances, TerminateInstances. Batch endpoints accept up to 100 IDs per call — chunk larger sets. These return a TaskId-style acknowledgement, not a completion; poll state if the caller needs certainty.
Every call can throw TencentCloudSDKException. Classify before retrying:
use TencentCloud\Common\Exception\TencentCloudSDKException;
try {
$resp = $client->DescribeInstances($req);
} catch (TencentCloudSDKException $e) {
// $e->getErrorCode(), $e->getMessage(), $e->getRequestId()
if (in_array($e->getErrorCode(), ['RequestLimitExceeded', 'InternalError'])) {
throw new RetryableCvmException($e->getMessage(), previous: $e);
}
throw $e;
}
Retry only on RequestLimitExceeded, InternalError, and 5xx-ish codes. Never retry InvalidParameter*, UnauthorizedOperation, ResourceInsufficient (that one needs a different zone/SKU, not a retry). Use exponential backoff with jitter; rate limits are per-region, per-action.
config/services.php → a tencent_cvm block with credentials and default region.CvmManager singleton in the container.ProvisionInstance, TerminateInstance, SnapshotInstance) so slow polls don't block HTTP requests. Set $tries and a backoff array on the job rather than retrying inline.request_id from every response alongside your own correlation ID — Tencent support will ask for it.instance_id on your own model; never reconstruct instance identity from tags alone.CreateImage + DescribeImages for golden-image pipelines; InquiryPriceRunInstances before RunInstances to show the user a cost estimate or enforce a budget check. Price inquiries are free and fast — always run them in a dry-run path.
This repo has zero community signal. 0 stars, 0 dependents, no issues, no wiki. It's a build artifact. Bugs you find belong in the monorepo, and answers live in Tencent's docs and Chinese-language forums. Treat the source as read-only and never patch vendor/ — regenerate or upgrade instead.
Region vs endpoint. Every CvmClient is bound to one region at construction. There is no cross-region call. DescribeRegions and DescribeZones still need a region argument (use any valid one). Don't try to reuse a client across regions by mutating it — build a new one.
Timeouts are the default 60s/60s. RunInstances for GPU or bare-metal SKUs regularly exceeds this on the server side. Bump setReqTimeout to 120–180 for create operations only; keep reads tight so a hung call doesn't pin a queue worker.
Response fields are nullable. Any property the API didn't return is null, and list getters can return null or [] depending on the action. Use getInstanceSet() ?? [] and null-safe traversal. Don't assume getTotalCount() is populated on non-list actions.
toJsonString() emits API-native PascalCase, not your app's snake_case. If you're logging payloads into a JSON column, be aware the shape won't match your Eloquent models. Prefer explicit getter mapping into a DTO over dumping raw JSON.
Setter chaining is inconsistent across generated versions. Some model setters return $this, some return void. Don't write (new Req())->setFoo(...)->setBar(...) unless you've checked the specific class.
PHP 8 compatibility depends on tencentcloud/common. Older releases emit dynamic-property deprecations and, in a few cases, fatal on union-typed returns. Pin ^3.x+ of common, and run your test suite on the PHP version you deploy (8.2/8.3).
Credentials leak into stack traces. TencentCloudSDKException messages sometimes interpolate request context. Scrub before report() in production, and never log the Credential object.
ClientToken rules. Typically ≤64 ASCII characters, and the same token must be reused verbatim on retry — a new UUID per attempt defeats the purpose. Generate it once when the job is dispatched, store it on your job record.
Eventual consistency. A freshly created instance may not appear in a DescribeInstances filter for a few seconds. Poll on instance-id, not on tag filters, or you'll get false negatives and re-provision.
Cost-bearing vs free calls. Describe* is free. RunInstances, TerminateInstances, and CreateImage have real billing/retention implications. There is no sandbox environment — do integration testing in a dedicated account/VPC with a hard InstanceCount cap, and use InquiryPriceRunInstances as a pre-flight check.
IAM. Use least privilege: a read-only dashboard only needs DescribeInstances/DescribeImages; a provisioning worker needs RunInstances scoped by tag condition. Do not ship QcloudCVMFullAccess to production.
StoppedMode is a billing decision, not a technical one. STOP_CHARGING releases the public IP; KEEP_CHARGING keeps it and keeps billing. Pick deliberately, and surface it in your UI.
Don't build a generic SDK wrapper abstraction. The models are already typed; a hand-rolled "CVM repository" that returns arrays throws away the one thing the package gives you. Wrap at the workflow level (Provision, Snapshot, Terminate) and let the SDK types flow through.
How can I help you explore Laravel packages today?