Skip to content

Commit 21db1ab

Browse files
authored
bugfix: align mpcopy with copyobject metadata directive (#3293)
1 parent 6844cc6 commit 21db1ab

4 files changed

Lines changed: 28 additions & 13 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"type": "bugfix",
4+
"category": "S3",
5+
"description": "Updates `MultipartCopy` to fully align with `CopyObject` metadata directive behavior. When `$config['metadata_directive']` is set to `COPY` (default), source object metadata takes precedence over any matching values provided in `$config['params']`."
6+
}
7+
]

src/S3/MultipartCopy.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ class MultipartCopy extends AbstractUploadManager
1313
getInitiateParams as private traitGetInitiateParams;
1414
}
1515

16+
private const VALID_METADATA_DIRECTIVES = [
17+
'COPY' => true,
18+
'REPLACE' => true,
19+
];
20+
1621
/**
1722
* Metadata fields that can be copied from the source object
1823
* to the destination during a multipart copy.
@@ -70,10 +75,10 @@ class MultipartCopy extends AbstractUploadManager
7075
* source object metadata to the destination. Set to 'COPY' to
7176
* automatically forward metadata fields (Metadata, CacheControl,
7277
* ContentDisposition, ContentEncoding, ContentLanguage, ContentType,
73-
* Expires) from the source object. Set to 'REPLACE' to suppress automatic
74-
* metadata copying (you must then provide your own metadata via the
75-
* 'params' option). User-provided values in 'params' always take
76-
* precedence over source metadata.
78+
* Expires) from the source object. When set to 'COPY', source metadata
79+
* takes precedence and any matching fields provided in 'params' are
80+
* ignored. Set to 'REPLACE' to suppress automatic metadata copying and
81+
* use your own values via the 'params' option.
7782
* - source_metadata: (Aws\ResultInterface) The result of a HeadObject call
7883
* on the copy source. If not provided, the SDK makes a HeadObject request
7984
* to obtain the source object's size and metadata. Providing this avoids
@@ -211,7 +216,7 @@ protected function getInitiateParams()
211216

212217
$directive = strtoupper($this->config['metadata_directive'] ?? 'COPY');
213218

214-
if (!in_array($directive, ['COPY', 'REPLACE'], true)) {
219+
if (!isset(self::VALID_METADATA_DIRECTIVES[$directive])) {
215220
throw new \InvalidArgumentException(
216221
"Invalid metadata_directive value '$directive'."
217222
. " Must be 'COPY' or 'REPLACE'."
@@ -221,7 +226,7 @@ protected function getInitiateParams()
221226
if ($directive === 'COPY') {
222227
$sourceMetadata = $this->getSourceMetadata();
223228
foreach (self::$copyMetadataFields as $field) {
224-
if (!isset($params[$field]) && !empty($sourceMetadata[$field])) {
229+
if (!empty($sourceMetadata[$field])) {
225230
$params[$field] = $sourceMetadata[$field];
226231
}
227232
}

src/S3/ObjectCopier.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
*
1818
* Makes a HeadObject call on the source to determine object size. Objects
1919
* below the multipart threshold (default 5 GB) are copied with a single
20-
* CopyObject call using MetadataDirective: COPY. Objects above the threshold
21-
* use MultipartCopy, which also preserves source metadata by default.
20+
* CopyObject call using MetadataDirective: COPY.
21+
*
22+
* Objects above the threshold use MultipartCopy, which preserves source metadata by default.
23+
* When the multipart path is used with the default metadata_directive ('COPY'),
24+
* metadata fields in 'params' (e.g. Metadata, ContentType, CacheControl)
25+
* are overridden by the source object's values.
2226
*/
2327
class ObjectCopier implements PromisorInterface
2428
{

tests/S3/MultipartCopyTest.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ public function testMetadataDirectiveReplaceSuppressesSourceMetadata()
310310
$this->assertArrayNotHasKey('ContentDisposition', $initiateParams);
311311
}
312312

313-
public function testUserParamsOverrideSourceMetadata()
313+
public function testSourceMetadataOverridesUserParamsWhenCopy()
314314
{
315315
$client = $this->getTestClient('s3');
316316
$url = 'http://foo.s3.amazonaws.com/bar';
@@ -344,10 +344,9 @@ public function testUserParamsOverrideSourceMetadata()
344344
]);
345345
$uploader->upload();
346346

347-
// User-provided params should take precedence
348-
$this->assertSame('text/plain', $initiateParams['ContentType']);
349-
$this->assertSame(['user-key' => 'user-value'], $initiateParams['Metadata']);
350-
// Source metadata that was NOT overridden should still be copied
347+
// Source metadata takes precedence over user-provided params when directive is COPY
348+
$this->assertSame('application/pdf', $initiateParams['ContentType']);
349+
$this->assertSame(['source-key' => 'source-value'], $initiateParams['Metadata']);
351350
$this->assertSame('max-age=3600', $initiateParams['CacheControl']);
352351
}
353352

0 commit comments

Comments
 (0)