Skip to content

S3Client

Minimal S3 REST client using AWS Signature Version 4, deliberately not built on aws/aws-sdk-php (a heavy dependency pulling in a client for every AWS service) for the operations a session or filesystem backend needs: get, put, delete, head and list.

Path-style requests, so endpoint also works against any S3-compatible service (MinIO, etc). The bucket is assumed to already exist, bucket lifecycle is normally managed outside the app (IaC), unlike Azure’s implicit per-account containers.

Anything beyond those five operations, multipart upload, tagging, versioning, is deliberately absent, but reachable: S3Client::request() performs the SigV4 signing and hands back the raw PSR-7 response, so a caller can implement the operation it needs without reimplementing the signature.

final class S3Client implements ListableObjectStoreClientInterface

ImplementsListableObjectStoreClientInterface
SourceS3Client.php

public function __construct(ClientInterface $httpClient, string $region, string $accessKeyId, string $secretAccessKey, string $bucket, ?string $endpoint = null, Psr17Factory $psr17 = new Psr17Factory(…)): mixed

ParameterTypeDescription
$httpClientClientInterface
$regionstring
$accessKeyIdstring
$secretAccessKeystring
$bucketstring
$endpoint?``string
$psr17Psr17Factory

Returns mixed

MethodDescription
delete(string $key): voidA 404 returns normally, so deleting a key that is not there is not an error; any other 4xx/5xx raises S3StorageException.
get(string $key): ?stringA 404 is reported as null; every other 4xx/5xx raises S3StorageException, as does a transport failure.
head(string $key): ?ObjectMetadataObject metadata without transferring the body, or null if the object does not exist.
listObjects(string $prefix = '', string $delimiter = '', ?string $continuationToken = null, int $maxKeys = 1000): ObjectListingListObjectsV2 under the hood: $continuationToken round-trips NextContinuationToken verbatim, and $delimiter groups into CommonPrefixes the same way the AWS console’s “folder” view does.
put(string $key, string $body): voidThe whole body is sent in a single signed PUT; there is no multipart upload, so the payload must fit one request.
request(string $method, string $key = '', array<string, string> $query = [], ?string $body = null): ResponseInterfaceSend an arbitrary signed request to this client’s bucket and return the raw response, for operations this class does not model itself.

public function delete(string $key): void

A 404 returns normally, so deleting a key that is not there is not an error; any other 4xx/5xx raises S3StorageException.

Best-effort: a key that does not exist is not an error.

ParameterTypeDescription
$keystring
ThrowsWhen
ObjectStoreExceptionOn a transport or provider failure.

public function get(string $key): ?string

A 404 is reported as null; every other 4xx/5xx raises S3StorageException, as does a transport failure.

No retry is attempted.

ParameterTypeDescription
$keystring

Returns ?``string

ThrowsWhen
ObjectStoreExceptionOn a transport or provider failure, as distinct from a missing object.

public function head(string $key): ?ObjectMetadata

Object metadata without transferring the body, or null if the object does not exist.

ParameterTypeDescription
$keystring

Returns ?ObjectMetadata

public function listObjects(string $prefix = '', string $delimiter = '', ?string $continuationToken = null, int $maxKeys = 1000): ObjectListing

ListObjectsV2 under the hood: $continuationToken round-trips NextContinuationToken verbatim, and $delimiter groups into CommonPrefixes the same way the AWS console’s “folder” view does.

With $delimiter empty, every matching key comes back as an ObjectSummary in ObjectListing::$objects — a fully recursive listing. With $delimiter set, a key is only listed that way when $prefix (plus nothing else) reaches it before the first occurrence of $delimiter; everything past that point is collapsed into one entry per distinct prefix-up-to-and-including-the-delimiter in ObjectListing::$commonPrefixes instead — the “one directory level” view every provider’s own console uses.

$continuationToken must be null on the first call and, for a truncated result, ObjectListing::$nextContinuationToken verbatim on the next — it is opaque, provider specific, and never meant to be inspected or constructed by a caller.

ParameterTypeDescription
$prefixstring
$delimiterstring
$continuationToken?``string
$maxKeysint

Returns ObjectListing

ThrowsWhen
ObjectStoreExceptionIf S3 answers 4xx/5xx, or its response body was not the XML this expects.

public function put(string $key, string $body): void

The whole body is sent in a single signed PUT; there is no multipart upload, so the payload must fit one request.

The bucket must already exist, this client never creates one.

ParameterTypeDescription
$keystring
$bodystring
ThrowsWhen
ObjectStoreExceptionIf the write does not succeed.

public function request(string $method, string $key = '', array<string, string> $query = [], ?string $body = null): ResponseInterface

Send an arbitrary signed request to this client’s bucket and return the raw response, for operations this class does not model itself.

query parameters, signed as part of the request

ParameterTypeDescription
$methodstring
$keystring
$queryarray``<``string``, ``string``>query parameters, signed as part of the request
$body?``string

Returns ResponseInterface