All Products
Search
Document Center

Object Storage Service:Multipart copy (PHP SDK V2)

Last Updated:Mar 19, 2026

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:

  1. `InitiateMultipartUpload` — creates an upload task and returns an uploadId.

  2. `UploadPartCopy` — copies a byte range from the source object as one part; repeat for each part and collect the ETag returned per part.

  3. `CompleteMultipartUpload` — assembles all parts into the final object using the uploadId and 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