From c1273ce91c6d277cab9aeac907525a7047a3dd8a Mon Sep 17 00:00:00 2001 From: "brecht.vermeersch" Date: Sat, 8 Nov 2025 07:26:13 +0100 Subject: [PATCH 1/2] add config options --- README.md | 39 +++++++++++++++ src/AzureBlobStorageAdapter.php | 80 +++++++++++++++++++++++++++---- src/Support/ConfigArrayParser.php | 54 +++++++++++++++++++++ 3 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 src/Support/ConfigArrayParser.php diff --git a/README.md b/README.md index 5164143..dbe947e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,45 @@ $adapter = new AzureBlobStorageAdapter( ); ``` +### Upload transfer tuning + +When writing files, you can control the upload behavior via Flysystem's Config on write/writeStream calls: + +- initialTransferSize: int (bytes) — size threshold for first transfer; smaller blobs are uploaded in a single request; larger ones switch to chunked upload. +- maximumTransferSize: int (bytes) — chunk size for block uploads. +- maximumConcurrency: int — number of concurrent workers for parallel uploads. + +Important: The three options above must be provided as ints. If a non-int value is provided, a RuntimeException will be thrown. + +Example: + +```php +use League\Flysystem\Config; + +$filesystem->write('path/to/file.txt', $contents, new Config([ + 'initialTransferSize' => 64 * 1024 * 1024, // 64MB + 'maximumTransferSize' => 8 * 1024 * 1024, // 8MB + 'maximumConcurrency' => 8, +])); +``` + +### HTTP headers + +You can also provide HTTP headers to be stored with the blob by passing the `httpHeaders` config key. +Example (array): + +```php +$filesystem->write('path/to/file.txt', $contents, new Config([ + 'httpHeaders' => [ + 'cacheControl' => 'public, max-age=31536000', + 'contentDisposition' => 'inline', + 'contentEncoding' => 'gzip', + 'contentLanguage' => 'en', + 'contentType' => 'text/plain', + ], +])); +``` + Note that for direct public URLs to work, your container must be configured with public access. If your container is private, you should use the default SAS token approach. ## Documentation diff --git a/src/AzureBlobStorageAdapter.php b/src/AzureBlobStorageAdapter.php index df081b4..25edee4 100644 --- a/src/AzureBlobStorageAdapter.php +++ b/src/AzureBlobStorageAdapter.php @@ -32,6 +32,7 @@ use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use League\MimeTypeDetection\FinfoMimeTypeDetector; use League\MimeTypeDetection\MimeTypeDetector; +use AzureOss\FlysystemAzureBlobStorage\Support\ConfigArrayParser; final class AzureBlobStorageAdapter implements FilesystemAdapter, ChecksumProvider, TemporaryUrlGenerator, PublicUrlGenerator { @@ -85,35 +86,96 @@ public function directoryExists(string $path): bool public function write(string $path, string $contents, Config $config): void { - $this->upload($path, $contents); + $this->upload($path, $contents, $config); } public function writeStream(string $path, $contents, Config $config): void { - $this->upload($path, $contents); + $this->upload($path, $contents, $config); } /** * @param string|resource $contents */ - private function upload(string $path, $contents): void + private function upload(string $path, $contents, ?Config $config = null): void { try { - $path = $this->prefixer->prefixPath($path); - $mimetype = $this->mimeTypeDetector->detectMimetype($path, $contents); + $options = $this->buildUploadOptionsFromConfig($config); - $options = new UploadBlobOptions( - contentType: $mimetype, - ); + if ($options->httpHeaders->contentType === "" && is_string($contents)) { + $options->httpHeaders->contentType = $this->mimeTypeDetector->detectMimeTypeFromBuffer($contents) ?? ""; + } $this->containerClient - ->getBlobClient($path) + ->getBlobClient($this->prefixer->prefixPath($path)) ->upload($contents, $options); } catch (\Throwable $e) { throw UnableToWriteFile::atLocation($path, previous: $e); } } + private function buildUploadOptionsFromConfig(?Config $config): UploadBlobOptions + { + $options = new UploadBlobOptions(); + + if ($config === null) { + return $options; + } + + $data = $config->toArray(); + + $initialTransferSize = ConfigArrayParser::parseIntFromArray($data, 'initialTransferSize'); + if ($initialTransferSize !== null) { + $options->initialTransferSize = $initialTransferSize; + } + + $maximumTransferSize = ConfigArrayParser::parseIntFromArray($data, 'maximumTransferSize'); + if ($maximumTransferSize !== null) { + $options->maximumTransferSize = $maximumTransferSize; + } + + $maximumConcurrency = ConfigArrayParser::parseIntFromArray($data, 'maximumConcurrency'); + if ($maximumConcurrency !== null) { + $options->maximumConcurrency = $maximumConcurrency; + } + + $headers = ConfigArrayParser::parseArrayFromArray($data, 'httpHeaders'); + if ($headers !== null) { + $cacheControl = ConfigArrayParser::parseStringFromArray($headers, 'cacheControl', 'httpHeaders.'); + if ($cacheControl !== null) { + $options->httpHeaders->cacheControl = $cacheControl; + } + + $contentDisposition = ConfigArrayParser::parseStringFromArray($headers, 'contentDisposition', 'httpHeaders.'); + if ($contentDisposition !== null) { + $options->httpHeaders->contentDisposition = $contentDisposition; + } + + $contentEncoding = ConfigArrayParser::parseStringFromArray($headers, 'contentEncoding', 'httpHeaders.'); + if ($contentEncoding !== null) { + $options->httpHeaders->contentEncoding = $contentEncoding; + } + + $contentHash = ConfigArrayParser::parseStringFromArray($headers, 'contentHash', 'httpHeaders.'); + if ($contentHash !== null) { + $options->httpHeaders->contentHash = $contentHash; + } + + $contentLanguage = ConfigArrayParser::parseStringFromArray($headers, 'contentLanguage', 'httpHeaders.'); + if ($contentLanguage !== null) { + $options->httpHeaders->contentLanguage = $contentLanguage; + } + + $contentType = ConfigArrayParser::parseStringFromArray($headers, 'contentType', 'httpHeaders.'); + if ($contentType !== null) { + $options->httpHeaders->contentType = $contentType; + } + } + + return $options; + } + + public function read(string $path): string { try { diff --git a/src/Support/ConfigArrayParser.php b/src/Support/ConfigArrayParser.php new file mode 100644 index 0000000..c795dd1 --- /dev/null +++ b/src/Support/ConfigArrayParser.php @@ -0,0 +1,54 @@ + $data + */ + public static function parseIntFromArray(array $data, string $key): ?int + { + if (!array_key_exists($key, $data) || $data[$key] === null) { + return null; + } + if (!is_int($data[$key])) { + throw new \RuntimeException(sprintf('%s must be an int.', $key)); + } + return $data[$key]; + } + + /** + * @param array $data + * @return array|null + */ + public static function parseArrayFromArray(array $data, string $key): ?array + { + $value = $data[$key] ?? null; + if ($value === null) { + return null; + } + if (!is_array($value)) { + throw new \RuntimeException(sprintf('%s must be an array.', $key)); + } + return $value; + } + + /** + * @param array $data + */ + public static function parseStringFromArray(array $data, string $key, string $contextPrefix = ''): ?string + { + $value = $data[$key] ?? null; + if ($value === null) { + return null; + } + if (!is_string($value)) { + $fullKey = $contextPrefix !== '' ? $contextPrefix . $key : $key; + throw new \RuntimeException(sprintf('%s must be a string.', $fullKey)); + } + return $value; + } +} From 5aa83ee2334e751fafda777cfb7424509aa5d670 Mon Sep 17 00:00:00 2001 From: "brecht.vermeersch" Date: Sat, 8 Nov 2025 07:28:36 +0100 Subject: [PATCH 2/2] add config options --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index dbe947e..6d857a6 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,6 @@ When writing files, you can control the upload behavior via Flysystem's Config o - maximumTransferSize: int (bytes) — chunk size for block uploads. - maximumConcurrency: int — number of concurrent workers for parallel uploads. -Important: The three options above must be provided as ints. If a non-int value is provided, a RuntimeException will be thrown. - Example: ```php @@ -79,9 +77,6 @@ $filesystem->write('path/to/file.txt', $contents, new Config([ ### HTTP headers -You can also provide HTTP headers to be stored with the blob by passing the `httpHeaders` config key. -Example (array): - ```php $filesystem->write('path/to/file.txt', $contents, new Config([ 'httpHeaders' => [