All Products
Search
Document Center

Object Storage Service:Multipart upload (PHP SDK V2)

Last Updated:Mar 20, 2026

Multipart upload splits a large object into multiple parts and uploads them independently. After all parts are uploaded, call CompleteMultipartUpload to assemble them into a single object.

Prerequisites

Before you begin, ensure that you have:

How it works

A multipart upload follows three steps:

  1. Initiate — Call InitiateMultipartUpload to get a globally unique upload ID from OSS.

  2. Upload parts — Call UploadPart once for each part, passing the upload ID and a part number.

  3. Complete — Call CompleteMultipartUpload with the list of uploaded parts to assemble the final object.

Part upload behavior:

  • Uploading a new part with an existing part number overwrites the previous data for that part.

  • OSS returns the MD5 hash of each received part in the ETag response header.

  • OSS validates integrity by comparing the MD5 it calculates against the SDK-calculated value. A mismatch returns an InvalidDigest error.

Upload a large file in parts

The following example splits a local file into 5 MB parts, uploads each part sequentially, and completes the upload. The endpoint is set to cn-hangzhou (public endpoint). If you access OSS from another Alibaba Cloud service in the same region, use the internal endpoint instead.

<?php

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

use AlibabaCloud\Oss\V2 as Oss;

// Parse required command-line arguments: --region, --bucket, --key
// Optional: --endpoint
$optsdesc = [
    "region"   => ['help' => 'The region in which the bucket is located.', 'required' => True],
    "endpoint" => ['help' => 'The domain names that other services can use to access OSS.', 'required' => False],
    "bucket"   => ['help' => 'The name of the bucket', 'required' => True],
    "key"      => ['help' => 'The name of the object', 'required' => True],
];

$longopts = \array_map(function ($key) { return "$key:"; }, array_keys($optsdesc));
$options  = getopt("", $longopts);

foreach ($optsdesc as $key => $value) {
    if ($value['required'] === True && empty($options[$key])) {
        echo "Error: the following arguments are required: --$key, " . $value['help'] . PHP_EOL;
        exit(1);
    }
}

$region = $options["region"];
$bucket = $options["bucket"];
$key    = $options["key"];

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

$cfg = Oss\Config::loadDefault();
$cfg->setCredentialsProvider($credentialsProvider);
$cfg->setRegion($region);
$cfg->setEndpoint('http://oss-cn-hangzhou.aliyuncs.com');

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

// Step 1: Initiate the multipart upload and obtain an upload ID
$initResult = $client->initiateMultipartUpload(
    new Oss\Models\InitiateMultipartUploadRequest(
        bucket: $bucket,
        key: $key
    )
);

// Step 2: Split the file and upload each part
$bigFileName = "/Users/yourLocalPath/yourFileName"; // Replace with the path to your local file
$partSize    = 5 * 1024 * 1024;                     // 5 MB per part
$fileSize    = filesize($bigFileName);
$partsNum    = intdiv($fileSize, $partSize) + intval(1);
$parts       = [];

$i    = 1;
$file = new \GuzzleHttp\Psr7\LazyOpenStream($bigFileName, 'rb');

while ($i <= $partsNum) {
    $partResult = $client->uploadPart(
        new Oss\Models\UploadPartRequest(
            bucket:        $bucket,
            key:           $key,
            partNumber:    $i,
            uploadId:      $initResult->uploadId,
            contentLength: null,   // Optional: length of the part body
            contentMd5:    null,   // Optional: The MD5 hash of the part content for validation.
            trafficLimit:  null,   // Optional: The traffic limit.
            requestPayer:  null,   // Optional: The requester pays for the request.
            body:          new \GuzzleHttp\Psr7\LimitStream($file, $partSize, ($i - 1) * $partSize)
        )
    );

    // Collect the part number and ETag — both are required to complete the upload
    $parts[] = new Oss\Models\UploadPart(
        partNumber: $i,
        etag:       $partResult->etag
    );

    $i++;
}

// Step 3: Assemble all parts into the final object
$comResult = $client->completeMultipartUpload(
    new Oss\Models\CompleteMultipartUploadRequest(
        bucket:                  $bucket,
        key:                     $key,
        uploadId:                $initResult->uploadId,
        acl:                     null, // Optional: set the ACL of the assembled object
        completeMultipartUpload: new Oss\Models\CompleteMultipartUpload(
            parts: $parts
        )
    )
);

printf(
    'status code: %s' . PHP_EOL .
    'request ID: %s' . PHP_EOL .
    'result: %s' . PHP_EOL,
    $comResult->statusCode,
    $comResult->requestId,
    var_export($comResult, true)
);

Replace the following placeholders before running the code:

PlaceholderDescription
/Users/yourLocalPath/yourFileNameAbsolute path to the local file you want to upload
--region argumentRegion ID of your bucket, for example cn-hangzhou
--bucket argumentName of your target bucket
--key argumentObject key (name) to assign in OSS

Upload with a callback

To notify an application server after the upload completes, attach an upload callback to CompleteMultipartUpload. The initiation and part-upload steps are identical to the basic example above — only the completion call changes.

<?php

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

use AlibabaCloud\Oss\V2 as Oss;

$optsdesc = [
    "region"   => ['help' => 'The region in which the bucket is located.', 'required' => True],
    "endpoint" => ['help' => 'The domain names that other services can use to access OSS.', 'required' => False],
    "bucket"   => ['help' => 'The name of the bucket', 'required' => True],
    "key"      => ['help' => 'The name of the object', 'required' => True],
];

$longopts = \array_map(function ($key) { return "$key:"; }, array_keys($optsdesc));
$options  = getopt("", $longopts);

foreach ($optsdesc as $key => $value) {
    if ($value['required'] === True && empty($options[$key])) {
        echo "Error: the following arguments are required: --$key, " . $value['help'] . PHP_EOL;
        exit(1);
    }
}

$region = $options["region"];
$bucket = $options["bucket"];
$key    = $options["key"];

$credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();

$cfg = Oss\Config::loadDefault();
$cfg->setCredentialsProvider($credentialsProvider);
$cfg->setRegion($region);
$cfg->setEndpoint('http://oss-cn-hangzhou.aliyuncs.com');

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

// Initiate
$initResult = $client->initiateMultipartUpload(
    new Oss\Models\InitiateMultipartUploadRequest(
        bucket: $bucket,
        key: $key
    )
);

// Upload parts (same logic as the basic example)
$bigFileName = "/Users/yourLocalPath/yourFileName";
$partSize    = 5 * 1024 * 1024;
$fileSize    = filesize($bigFileName);
$partsNum    = intdiv($fileSize, $partSize) + intval(1);
$parts       = [];

$i    = 1;
$file = new \GuzzleHttp\Psr7\LazyOpenStream($bigFileName, 'rb');

while ($i <= $partsNum) {
    $partResult = $client->uploadPart(
        new Oss\Models\UploadPartRequest(
            bucket:        $bucket,
            key:           $key,
            partNumber:    $i,
            uploadId:      $initResult->uploadId,
            contentLength: null,
            contentMd5:    null,
            trafficLimit:  null,
            requestPayer:  null,
            body:          new \GuzzleHttp\Psr7\LimitStream($file, $partSize, ($i - 1) * $partSize)
        )
    );

    $parts[] = new Oss\Models\UploadPart(
        partNumber: $i,
        etag:       $partResult->etag
    );

    $i++;
}

// Build the callback payload — OSS will POST this to your server after assembly
$call_back_url = "http://www.example.com/callback"; // Replace with your server's endpoint

// The callback body uses OSS system variables ({bucket}, {object}) and
// custom variables ({x:var1}, {x:var2}) that you define in callbackVar.
$callback_body_template = "bucket={bucket}&object={object}&my_var_1={var1}&my_var_2={var2}";
$callback_body = str_replace(
    ['{bucket}', '{object}', '{var1}', '{var2}'],
    [$bucket, $key, 'value1', 'value2'],
    $callback_body_template
);

// Both callback and callbackVar must be Base64-encoded JSON
$callback = base64_encode(json_encode([
    "callbackUrl"  => $call_back_url,
    "callbackBody" => $callback_body,
]));

$callback_var = base64_encode(json_encode([
    "x:var1" => "value1",
    "x:var2" => "value2",
]));

// Complete — OSS triggers the callback after assembling the object
$comResult = $client->completeMultipartUpload(
    new Oss\Models\CompleteMultipartUploadRequest(
        bucket:                  $bucket,
        key:                     $key,
        uploadId:                $initResult->uploadId,
        acl:                     null,
        completeMultipartUpload: new Oss\Models\CompleteMultipartUpload(
            parts: $parts
        ),
        callback:    $callback,
        callbackVar: $callback_var,
    )
);

printf(
    'status code: %s' . PHP_EOL .
    'request ID: %s' . PHP_EOL .
    'result: %s' . PHP_EOL,
    $comResult->statusCode,
    $comResult->requestId,
    var_export($comResult, true)
);

Related topics