Copy objects using OSS SDK for PHP 2.0

Updated at:

Use the OSS PHP SDK V2 to copy objects within a versioning-enabled bucket. For objects up to 1 GB, use CopyObject. For objects larger than 1 GB, split them into parts and copy each part using UploadPartCopy.

Prerequisites

Before you begin, ensure that you have:

  • oss:GetObject and oss:PutObject permissions on both the source and destination buckets. For details, see Attach a custom policy to a RAM user.

  • (Optional) The version ID of the object you want to copy, if you need to copy a specific version.

Usage notes

  • The sample code uses region ID cn-hangzhou. By default, a public endpoint is used. To access OSS from other Alibaba Cloud services in the same region, use an internal endpoint. For a full list of regions and endpoints, see Regions and endpoints.

  • CopyObject copies objects up to 1 GB from a source bucket to a destination bucket in the same region. For objects larger than 1 GB, use multipart copy.

Copy an object

CopyObject copies the current version of an object by default. To copy a specific version, set the sourceVersionId parameter.

Version ID behavior:

  • No version ID specified: copies the current version. If the current version is a delete marker, OSS returns HTTP 404.

  • Version ID specified: copies that specific version. Delete markers cannot be copied.

  • Copy a previous version to the same bucket: that version becomes the current version, effectively restoring it.

Destination bucket behavior:

  • Versioning enabled: OSS generates a unique version ID for the destination object, returned in the x-oss-version-id response header.

  • Versioning disabled or suspended: OSS assigns a null version ID and overwrites any existing null version.

The following example copies a specific version of an object:

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use AlibabaCloud\Oss\V2 as Oss;

// Load credentials from environment variables.
$credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();

// Initialize the client.
$cfg = Oss\Config::loadDefault();
$cfg->setCredentialsProvider($credentialsProvider);
$cfg->setRegion('cn-hangzhou');                        // Replace with your region.
// $cfg->setEndpoint('<your-endpoint>');               // Uncomment to use an internal or custom endpoint.

$client = new Oss\Client($cfg);

// Copy the object.
$request = new Oss\Models\CopyObjectRequest(
    bucket: '<destination-bucket>',                    // Destination bucket name.
    key: '<destination-key>',                          // Destination object name.
    sourceVersionId: '<source-version-id>',            // Version ID of the source object. Remove this line to copy the current version.
);
$request->sourceBucket = '<source-bucket>';            // Source bucket name. Omit if copying within the same bucket.
$request->sourceKey    = '<source-key>';               // Source object name.

$result = $client->copyObject($request);

printf(
    'status code: %s' . PHP_EOL .                     // HTTP 200 indicates success.
    'request ID:  %s' . PHP_EOL,                       // Use the request ID to trace or debug the request.
    $result->statusCode,
    $result->requestId
);

Replace the following placeholders:

PlaceholderDescriptionExample
<destination-bucket>Name of the destination bucketmy-bucket
<destination-key>Name of the destination objecttarget/photo.jpg
<source-version-id>Version ID of the source objectCAEQMxiBgICAof2D0BYiIDJhMGE3N2M1YTI1NDUy
<source-bucket>Name of the source bucketmy-source-bucket
<source-key>Name of the source objectsource/photo.jpg

Copy objects using multipart copy

For objects larger than 1 GB, use UploadPartCopy to copy the object in parts.

UploadPartCopy copies data from the current version of an object by default. Specify sourceVersionId to copy a specific version.

Delete marker behavior:

  • No version ID specified and the current version is a delete marker: OSS returns 404 Not Found.

  • Version ID specified and that version is a delete marker: OSS returns 400 Bad Request.

The multipart copy workflow has four stages:

  1. Initiate: call initiateMultipartUpload to start the task and get an upload ID.

  2. Get object size: call headObject on the source to get its content length.

  3. Copy parts: split the object into 1 MB parts and call uploadPartCopy for each part. Collect the ETag from each response.

  4. Complete: call completeMultipartUpload with the part list to assemble the final object.

The following example copies an object larger than 1 GB using multipart copy:

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use AlibabaCloud\Oss\V2 as Oss;

// Load credentials from environment variables.
$credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();

// Initialize the client.
$cfg = Oss\Config::loadDefault();
$cfg->setCredentialsProvider($credentialsProvider);
$cfg->setRegion('cn-hangzhou');                        // Replace with your region.
// $cfg->setEndpoint('<your-endpoint>');               // Uncomment to use an internal or custom endpoint.

$client = new Oss\Client($cfg);

$destinationBucket = '<destination-bucket>';           // Destination bucket name.
$destinationKey    = '<destination-key>';              // Destination object name.
$sourceBucket      = '<source-bucket>';                // Source bucket name. Set to $destinationBucket if copying within the same bucket.
$sourceKey         = '<source-key>';                   // Source object name.
$sourceVersionId   = '<source-version-id>';            // Version ID of the source object. Remove this to copy the current version.

// Stage 1: Initiate the multipart copy task.
$initResult = $client->initiateMultipartUpload(
    new Oss\Models\InitiateMultipartUploadRequest(bucket: $destinationBucket, key: $destinationKey)
);
$uploadId = $initResult->uploadId;

// Stage 2: Get the size of the source object.
$headResult = $client->headObject(
    new Oss\Models\HeadObjectRequest(bucket: $sourceBucket, key: $sourceKey)
);
$objectSize = $headResult->contentLength;

// Stage 3: Copy the object in 1 MB parts.
$partSize = 1024 * 1024;                               // Part size in bytes (1 MB).
$partCount = intdiv($objectSize, $partSize) + 1;
$parts = [];

for ($i = 1; $i <= $partCount; $i++) {
    $partRequest = new Oss\Models\UploadPartCopyRequest(
        bucket:          $destinationBucket,
        key:             $destinationKey,
        partNumber:      $i,
        uploadId:        $uploadId,
        sourceVersionId: $sourceVersionId,             // Remove this line to copy the current version.
    );
    $partRequest->sourceBucket = $sourceBucket;
    $partRequest->sourceKey    = $sourceKey;
    $partRequest->sourceRange  = getPartRange($objectSize, $partSize, $i);

    $partResult = $client->uploadPartCopy($partRequest);

    $parts[] = new Oss\Models\UploadPart(
        partNumber: $i,
        etag:       $partResult->etag,                 // Save the ETag to complete the upload later.
    );
}

// Stage 4: Complete the multipart copy.
$completeResult = $client->completeMultipartUpload(
    new Oss\Models\CompleteMultipartUploadRequest(
        bucket:    $destinationBucket,
        key:       $destinationKey,
        uploadId:  $uploadId,
        completeMultipartUpload: new Oss\Models\CompleteMultipartUpload(parts: $parts),
    )
);

printf(
    'status code: %s' . PHP_EOL .                     // HTTP 200 indicates success.
    'request ID:  %s' . PHP_EOL .                     // Use the request ID to trace or debug the request.
    'result:      %s' . PHP_EOL,
    $completeResult->statusCode,
    $completeResult->requestId,
    var_export($completeResult, true)
);

/**
 * Calculates 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 Part number (1-based).
 * @return string         Byte range string, e.g., "bytes 0-1048575".
 */
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);
}

Replace the following placeholders:

PlaceholderDescriptionExample
<destination-bucket>Name of the destination bucketmy-bucket
<destination-key>Name of the destination objecttarget/large-file.zip
<source-bucket>Name of the source bucketmy-source-bucket
<source-key>Name of the source objectsource/large-file.zip
<source-version-id>Version ID of the source objectCAEQMxiBgICAof2D0BYiIDJhMGE3N2M1YTI1NDUy