Use the UploadPartCopy method to copy objects by splitting the source object into parts, copying each part independently, and assembling them into a complete object in the destination bucket. Both buckets must be in the same region.
Prerequisites
Before you begin, make sure you have:
Read permission on the source object and read/write permissions on the destination bucket.
AccessKey ID and AccessKey Secret stored as environment variables. For other credential options, see Configure access credentials for PHP.
Usage notes
The sample code uses the China (Hangzhou) region (
cn-hangzhou) with a public endpoint. To access OSS from another Alibaba Cloud service in the same region, use an internal endpoint instead. For supported regions and endpoints, see OSS regions and endpoints.Cross-region copy is not supported. For example, you cannot copy an object from a bucket in China (Hangzhou) to a bucket in China (Qingdao).
If a retention policy is configured on either the source or destination bucket, the copy fails with:
The object you specified is immutable.
How it works
Multipart copy uses three API calls in sequence:
`InitiateMultipartUpload` — creates an upload task and returns an
uploadId.`UploadPartCopy` — copies a byte range from the source object as one part; repeat for each part and collect the ETag returned per part.
`CompleteMultipartUpload` — assembles all parts into the final object using the
uploadIdand the list of part numbers with their ETags.
The sample code sets the part size to 64 MB and calculates the number of parts based on the source object size.
Copy an object using multipart copy
<?php
// Load Composer dependencies.
require_once __DIR__ . '/../vendor/autoload.php';
use AlibabaCloud\Oss\V2 as Oss;
// Define CLI arguments.
$optsdesc = [
"region" => ['help' => 'The region where the bucket is located.', 'required' => true],
"endpoint" => ['help' => 'The endpoint for OSS.', 'required' => false],
"bucket" => ['help' => 'The destination bucket name.', 'required' => true],
"key" => ['help' => 'The destination object name.', 'required' => true],
"src-bucket" => ['help' => 'The source bucket name (defaults to destination bucket).', 'required' => false],
"src-key" => ['help' => 'The source object name.', 'required' => true],
];
// Build getopt-compatible long options (each requires a value).
$longopts = array_map(fn($key) => "$key:", array_keys($optsdesc));
$options = getopt("", $longopts);
// Validate required arguments.
foreach ($optsdesc as $key => $meta) {
if ($meta['required'] && empty($options[$key])) {
echo "Error: --$key is required. {$meta['help']}" . PHP_EOL;
exit(1);
}
}
$region = $options["region"];
$bucket = $options["bucket"];
$key = $options["key"];
$srcKey = $options["src-key"];
// Load credentials from environment variables.
$credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();
// Configure the SDK.
$cfg = Oss\Config::loadDefault();
$cfg->setCredentialsProvider($credentialsProvider);
$cfg->setRegion($region);
$cfg->setEndpoint($options["endpoint"] ?? 'http://oss-cn-hangzhou.aliyuncs.com');
$client = new Oss\Client($cfg);
// Step 1: Initiate the multipart upload and get the upload ID.
$initResult = $client->initiateMultipartUpload(
new Oss\Models\InitiateMultipartUploadRequest(bucket: $bucket, key: $key)
);
$uploadId = $initResult->uploadId;
// Determine source bucket (defaults to destination bucket if not specified).
$sourceBucket = $options["src-bucket"] ?? $bucket;
// Step 2: Get the source object size.
$headResult = $client->headObject(
new Oss\Models\HeadObjectRequest(bucket: $sourceBucket, key: $srcKey)
);
$totalSize = $headResult->contentLength;
// Calculate part count at 64 MB per part.
$partSize = 64 * 1024 * 1024;
$partsNum = intdiv($totalSize, $partSize) + 1;
// Step 3: Copy each part by byte range and collect part descriptors.
$parts = [];
for ($i = 1; $i <= $partsNum; $i++) {
$partRequest = new Oss\Models\UploadPartCopyRequest(
bucket: $bucket,
key: $key,
partNumber: $i,
uploadId: $uploadId
);
$partRequest->sourceRange = getPartRange($totalSize, $partSize, $i);
$partRequest->sourceKey = $srcKey;
if (!empty($options["src-bucket"])) {
$partRequest->sourceBucket = $options["src-bucket"];
}
$partResult = $client->uploadPartCopy($partRequest);
$parts[] = new Oss\Models\UploadPart(
partNumber: $i,
etag: $partResult->etag,
);
}
// Step 4: Complete the multipart upload.
$comResult = $client->completeMultipartUpload(
new Oss\Models\CompleteMultipartUploadRequest(
bucket: $bucket,
key: $key,
uploadId: $uploadId,
completeMultipartUpload: new Oss\Models\CompleteMultipartUpload(parts: $parts),
)
);
printf(
"Status code: %s\nRequest ID: %s\nResult: %s\n",
$comResult->statusCode,
$comResult->requestId,
var_export($comResult, true)
);
/**
* Calculate the byte range for a given part.
*
* @param int $totalSize Total size of the source object in bytes.
* @param int $partSize Size of each part in bytes.
* @param int $partNumber 1-based part number.
* @return string Byte range string in the format "bytes {start}-{end}".
*/
function getPartRange(int $totalSize, int $partSize, int $partNumber): string
{
$start = ($partNumber - 1) * $partSize;
$end = min($partNumber * $partSize - 1, $totalSize - 1);
return sprintf('bytes %d-%d', $start, $end);
}References
For the complete sample, see GitHub sample.