From 08d986a1f817f9ccaddd4598ec46e1e0a0fa5ec9 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 3 Mar 2026 17:17:26 -0500 Subject: [PATCH 1/5] docs(ObjectStore/S3): clean up docblocks Signed-off-by: Josh --- lib/private/Files/ObjectStore/S3.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index e92346e4ccc01..daa268c63bc9c 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -1,7 +1,7 @@ parseParams($parameters); } - /** - * @return string the container or bucket name where objects are stored - * @since 7.0.0 - */ public function getStorageId() { return $this->id; } From fc7dc1489df8e4398b6226078c5774794efdcb0c Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 3 Mar 2026 17:38:36 -0500 Subject: [PATCH 2/5] refactor(ObjectStore/S3): harden initiateMultipartUpload UploadId validation Signed-off-by: Josh --- lib/private/Files/ObjectStore/S3.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index daa268c63bc9c..313a6e6c99c2a 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -36,15 +36,19 @@ public function getStorageId() { } public function initiateMultipartUpload(string $urn): string { - $upload = $this->getConnection()->createMultipartUpload([ + $request = [ 'Bucket' => $this->bucket, 'Key' => $urn, - ] + $this->getSSECParameters()); + ] + $this->getSSECParameters(); + + $result = $this->getConnection()->createMultipartUpload($request); $uploadId = $upload->get('UploadId'); - if ($uploadId === null) { - throw new Exception('No upload id returned'); + + if (!is_string($uploadId) || $uploadId === '') { + throw new Exception("Failed to initiate multipart upload for key '{$urn}': missing UploadId"); } - return (string)$uploadId; + + return $uploadId; } public function uploadMultipartPart(string $urn, string $uploadId, int $partId, $stream, $size): Result { From 99e0cba77fe7ed4233a62e96436a3b95669a5e63 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 9 Mar 2026 23:34:46 -0400 Subject: [PATCH 3/5] refactor(S3): add explanation for S3ConfigTrait use, defaults to all properties - Trait docblock (including shared context) - Docs for every property Signed-off-by: Josh --- .../Files/ObjectStore/S3ConfigTrait.php | 65 +++++++++++++------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/lib/private/Files/ObjectStore/S3ConfigTrait.php b/lib/private/Files/ObjectStore/S3ConfigTrait.php index 661d95c4f822d..103b5206afeb3 100644 --- a/lib/private/Files/ObjectStore/S3ConfigTrait.php +++ b/lib/private/Files/ObjectStore/S3ConfigTrait.php @@ -1,43 +1,70 @@ Date: Tue, 10 Mar 2026 00:21:41 -0400 Subject: [PATCH 4/5] refactor(S3): add typing for static analysis to IObjectStoreMultiPartUpload.php - Not quite there on runtime typing, but let's add for total static analysis - Adds docblocks for class and functions too Signed-off-by: Josh --- .../IObjectStoreMultiPartUpload.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php b/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php index 7989e27dfc851..1470e3f464372 100644 --- a/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php +++ b/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php @@ -10,30 +10,81 @@ use Aws\Result; /** + * Multipart upload capabilities for object stores. + * + * Implementations are expected to support the standard multipart lifecycle: + * initiate -> uploadMultipartPart (1..n) -> completeMultipartUpload | abortMultipartUpload. + * + * Notes: + * - Part IDs are expected to be positive integers starting at 1. + * - Callers should pass parts to completion in ascending part order unless an implementation documents otherwise. + * - Re-uploading the same part ID for the same uploadId may overwrite the previously uploaded part, + * depending on backend semantics. + * * @since 26.0.0 */ interface IObjectStoreMultiPartUpload { /** + * Start a multipart upload for the object identified by $urn. + * + * @param string $urn Object identifier in the object store namespace. + * @return string Backend upload identifier to be used for subsequent part operations. + * * @since 26.0.0 */ public function initiateMultipartUpload(string $urn): string; /** + * Upload one multipart chunk for an active multipart upload. + * + * @param string $urn Object identifier in the object store namespace. + * @param string $uploadId Upload identifier previously returned by initiateMultipartUpload(). + * @param int $partId Part number. + * @param resource|object $stream Stream payload for the part. Implementations may accept + * stream resources or stream-like objects. + * @param int $size Size of the part payload in bytes. + * @return Result Backend result metadata for the uploaded part (e.g. ETag/checksum fields if provided). + * * @since 26.0.0 */ public function uploadMultipartPart(string $urn, string $uploadId, int $partId, $stream, $size): Result; /** + * Complete an active multipart upload by assembling uploaded parts. + * + * May take a long time! + * + * @param string $urn Object identifier in the object store namespace. + * @param string $uploadId Upload identifier previously returned by initiateMultipartUpload(). + * @param array> $result Part metadata used for final assembly. + * Expected to contain backend-specific per-part information returned from uploadMultipartPart(), + * commonly including part number and ETag/checksum fields. + * @return int Size in bytes of the assembled object as stored after upload completion. + * * @since 26.0.0 */ public function completeMultipartUpload(string $urn, string $uploadId, array $result): int; /** + * Abort an active multipart upload. + * + * After aborting, uploaded parts associated with the uploadId are expected to be discarded by backend + * cleanup semantics. + * + * @param string $urn Object identifier in the object store namespace. + * @param string $uploadId Upload identifier previously returned by initiateMultipartUpload(). + * * @since 26.0.0 */ public function abortMultipartUpload(string $urn, string $uploadId): void; /** + * Retrieve already uploaded parts for a given multipart upload. + * + * @param string $urn Object identifier in the object store namespace. + * @param string $uploadId Upload identifier previously returned by initiateMultipartUpload(). + * @return array> Backend-specific list of uploaded part descriptors. + * * @since 26.0.0 */ public function getMultipartUploads(string $urn, string $uploadId): array; From 8d3f666ce8b9dafb2748f6971f6bab953b88e0a0 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 10 Mar 2026 09:13:59 -0400 Subject: [PATCH 5/5] chore: fix typo in refactor Signed-off-by: Josh --- lib/private/Files/ObjectStore/S3.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index 313a6e6c99c2a..a3963bea29b17 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -42,7 +42,7 @@ public function initiateMultipartUpload(string $urn): string { ] + $this->getSSECParameters(); $result = $this->getConnection()->createMultipartUpload($request); - $uploadId = $upload->get('UploadId'); + $uploadId = $result->get('UploadId'); if (!is_string($uploadId) || $uploadId === '') { throw new Exception("Failed to initiate multipart upload for key '{$urn}': missing UploadId");