From bcf203f04a9649ca767000e5128d1e68193a6ab5 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 10 Feb 2026 14:30:23 +0530 Subject: [PATCH 1/3] chore: align main with 0.18.x --- .github/workflows/tests.yml | 2 +- Dockerfile | 12 +- README.md | 244 +- composer.json | 16 +- composer.lock | 2473 +++++++++++++---- phpunit.xml | 1 + src/Storage/Compression/Algorithms/Brotli.php | 104 + src/Storage/Compression/Algorithms/GZIP.php | 43 + src/Storage/Compression/Algorithms/LZ4.php | 79 + src/Storage/Compression/Algorithms/Snappy.php | 38 + src/Storage/Compression/Algorithms/XZ.php | 38 + src/Storage/Compression/Algorithms/Zstd.php | 79 + src/Storage/Compression/Compression.php | 39 + src/Storage/Device.php | 23 +- src/Storage/Device/AWS.php | 106 + src/Storage/Device/Backblaze.php | 4 +- src/Storage/Device/DOSpaces.php | 4 +- src/Storage/Device/Linode.php | 4 +- src/Storage/Device/Local.php | 8 +- src/Storage/Device/S3.php | 186 +- src/Storage/Device/Telemetry.php | 146 + src/Storage/Device/Wasabi.php | 4 +- src/Storage/Exception/NotFoundException.php | 9 + src/Storage/Storage.php | 2 + .../Compression/Algorithms/BrotliTest.php | 108 + .../Compression/Algorithms/GZIPTest.php | 99 + .../Compression/Algorithms/LZ4Test.php | 77 + .../Compression/Algorithms/SnappyTest.php | 81 + .../Storage/Compression/Algorithms/XZTest.php | 77 + .../Compression/Algorithms/ZstdTest.php | 96 + .../Device/{S3Test.php => AWSTest.php} | 10 +- tests/Storage/Device/LocalTest.php | 13 +- tests/Storage/S3Base.php | 23 +- 33 files changed, 3397 insertions(+), 851 deletions(-) create mode 100644 src/Storage/Compression/Algorithms/Brotli.php create mode 100644 src/Storage/Compression/Algorithms/GZIP.php create mode 100644 src/Storage/Compression/Algorithms/LZ4.php create mode 100644 src/Storage/Compression/Algorithms/Snappy.php create mode 100644 src/Storage/Compression/Algorithms/XZ.php create mode 100644 src/Storage/Compression/Algorithms/Zstd.php create mode 100644 src/Storage/Compression/Compression.php create mode 100644 src/Storage/Device/AWS.php create mode 100644 src/Storage/Device/Telemetry.php create mode 100644 src/Storage/Exception/NotFoundException.php create mode 100644 tests/Storage/Compression/Algorithms/BrotliTest.php create mode 100644 tests/Storage/Compression/Algorithms/GZIPTest.php create mode 100644 tests/Storage/Compression/Algorithms/LZ4Test.php create mode 100644 tests/Storage/Compression/Algorithms/SnappyTest.php create mode 100644 tests/Storage/Compression/Algorithms/XZTest.php create mode 100644 tests/Storage/Compression/Algorithms/ZstdTest.php rename tests/Storage/Device/{S3Test.php => AWSTest.php} (68%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 43d7ca7e..9e51762c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,7 @@ jobs: strategy: fail-fast: false matrix: - devices: [BackblazeTest, DOSpacesTest, LinodeTest, LocalTest, S3Test, WasabiTest] + devices: [BackblazeTest, DOSpacesTest, LinodeTest, LocalTest, AWSTest, WasabiTest] steps: - name: checkout diff --git a/Dockerfile b/Dockerfile index 5523b6c3..2e9659f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN composer update \ --no-scripts \ --prefer-dist -FROM php:8.0-cli-alpine as compile +FROM php:8.1-cli-alpine as compile ENV PHP_ZSTD_VERSION="master" ENV PHP_BROTLI_VERSION="7ae4fcd8b81a65d7521c298cae49af386d1ea4e3" @@ -104,11 +104,11 @@ RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \ && echo "memory_limit=1024M" >> $PHP_INI_DIR/php.ini COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor -COPY --from=zstd /usr/local/lib/php/extensions/no-debug-non-zts-20200930/zstd.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ -COPY --from=brotli /usr/local/lib/php/extensions/no-debug-non-zts-20200930/brotli.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ -COPY --from=lz4 /usr/local/lib/php/extensions/no-debug-non-zts-20200930/lz4.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ -COPY --from=snappy /usr/local/lib/php/extensions/no-debug-non-zts-20200930/snappy.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ -COPY --from=xz /usr/local/lib/php/extensions/no-debug-non-zts-20200930/xz.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ +COPY --from=zstd /usr/local/lib/php/extensions/no-debug-non-zts-20210902/zstd.so /usr/local/lib/php/extensions/no-debug-non-zts-20210902/ +COPY --from=brotli /usr/local/lib/php/extensions/no-debug-non-zts-20210902/brotli.so /usr/local/lib/php/extensions/no-debug-non-zts-20210902/ +COPY --from=lz4 /usr/local/lib/php/extensions/no-debug-non-zts-20210902/lz4.so /usr/local/lib/php/extensions/no-debug-non-zts-20210902/ +COPY --from=snappy /usr/local/lib/php/extensions/no-debug-non-zts-20210902/snappy.so /usr/local/lib/php/extensions/no-debug-non-zts-20210902/ +COPY --from=xz /usr/local/lib/php/extensions/no-debug-non-zts-20210902/xz.so /usr/local/lib/php/extensions/no-debug-non-zts-20210902/ # Add Source Code COPY . /usr/src/code diff --git a/README.md b/README.md index f2e5cdd5..80d9ef6b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # Utopia Storage -[![Build Status](https://travis-ci.org/utopia-php/storage.svg?branch=master)](https://travis-ci.com/utopia-php/storage) +[![Build Status](https://travis-ci.org/utopia-php/ab.svg?branch=master)](https://travis-ci.com/utopia-php/storage) ![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/storage.svg) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord)](https://appwrite.io/discord) -Utopia Storage is a simple and lightweight library for managing application storage across multiple adapters. This library is designed to be easy to learn and use, with a consistent API regardless of the storage provider. This library is maintained by the [Appwrite team](https://appwrite.io). +Utopia Storage library is simple and lite library for managing application storage. It supports multiple storage adapters. We already support AWS S3 storage, Digitalocean Spaces storage, Backblaze B2 Cloud storage, Linode Object storage and Wasabi Cloud storage. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io). This library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project. + ## Getting Started Install using composer: @@ -15,246 +16,39 @@ Install using composer: composer require utopia-php/storage ``` -### Basic Usage - ```php upload('/local/path/to/file.png', 'destination/path/file.png'); - -// Check if file exists -$exists = $device->exists('destination/path/file.png'); - -// Read file contents -$contents = $device->read('destination/path/file.png'); - -// Delete a file -$device->delete('destination/path/file.png'); -``` - -## Available Adapters - -### Local Storage - -Use the local filesystem for storing files. - -```php -use Utopia\Storage\Storage; -use Utopia\Storage\Device\Local; - -// Initialize local storage -Storage::setDevice('files', new Local('/path/to/storage')); -``` - -### AWS S3 - -Store files in Amazon S3 or compatible services. - -```php -use Utopia\Storage\Storage; -use Utopia\Storage\Device\S3; - -// Initialize S3 storage -Storage::setDevice('files', new S3( - 'root', // Root path in bucket - 'YOUR_ACCESS_KEY', - 'YOUR_SECRET_KEY', - 'YOUR_BUCKET_NAME', - S3::US_EAST_1, // Region (default: us-east-1) - S3::ACL_PRIVATE // Access control (default: private) -)); - -// Available regions -// S3::US_EAST_1, S3::US_EAST_2, S3::US_WEST_1, S3::US_WEST_2, S3::AP_SOUTH_1, -// S3::AP_NORTHEAST_1, S3::AP_NORTHEAST_2, S3::AP_NORTHEAST_3, S3::AP_SOUTHEAST_1, -// S3::AP_SOUTHEAST_2, S3::EU_CENTRAL_1, S3::EU_WEST_1, S3::EU_WEST_2, S3::EU_WEST_3, -// And more - check the S3 class for all available regions - -// Available ACL options -// S3::ACL_PRIVATE, S3::ACL_PUBLIC_READ, S3::ACL_PUBLIC_READ_WRITE, S3::ACL_AUTHENTICATED_READ -``` - -### DigitalOcean Spaces - -Store files in DigitalOcean Spaces. - -```php -use Utopia\Storage\Storage; -use Utopia\Storage\Device\DOSpaces; - -// Initialize DO Spaces storage -Storage::setDevice('files', new DOSpaces( - 'root', // Root path in bucket - 'YOUR_ACCESS_KEY', - 'YOUR_SECRET_KEY', - 'YOUR_BUCKET_NAME', - DOSpaces::NYC3, // Region (default: nyc3) - DOSpaces::ACL_PRIVATE // Access control (default: private) -)); - -// Available regions -// DOSpaces::NYC3, DOSpaces::SGP1, DOSpaces::FRA1, DOSpaces::SFO2, DOSpaces::SFO3, DOSpaces::AMS3 -``` - -### Backblaze B2 - -Store files in Backblaze B2 Cloud Storage. - -```php -use Utopia\Storage\Storage; -use Utopia\Storage\Device\Backblaze; - -// Initialize Backblaze storage -Storage::setDevice('files', new Backblaze( - 'root', // Root path in bucket - 'YOUR_ACCESS_KEY', - 'YOUR_SECRET_KEY', - 'YOUR_BUCKET_NAME', - Backblaze::US_WEST_004, // Region (default: us-west-004) - Backblaze::ACL_PRIVATE // Access control (default: private) -)); - -// Available regions (clusters) -// Backblaze::US_WEST_000, Backblaze::US_WEST_001, Backblaze::US_WEST_002, -// Backblaze::US_WEST_004, Backblaze::EU_CENTRAL_003 -``` - -### Linode Object Storage - -Store files in Linode Object Storage. - -```php -use Utopia\Storage\Storage; -use Utopia\Storage\Device\Linode; - -// Initialize Linode storage -Storage::setDevice('files', new Linode( - 'root', // Root path in bucket - 'YOUR_ACCESS_KEY', - 'YOUR_SECRET_KEY', - 'YOUR_BUCKET_NAME', - Linode::EU_CENTRAL_1, // Region (default: eu-central-1) - Linode::ACL_PRIVATE // Access control (default: private) -)); - -// Available regions -// Linode::EU_CENTRAL_1, Linode::US_SOUTHEAST_1, Linode::US_EAST_1, Linode::AP_SOUTH_1 -``` - -### Wasabi Cloud Storage - -Store files in Wasabi Cloud Storage. - -```php -use Utopia\Storage\Storage; -use Utopia\Storage\Device\Wasabi; - -// Initialize Wasabi storage -Storage::setDevice('files', new Wasabi( - 'root', // Root path in bucket - 'YOUR_ACCESS_KEY', - 'YOUR_SECRET_KEY', - 'YOUR_BUCKET_NAME', - Wasabi::EU_CENTRAL_1, // Region (default: eu-central-1) - Wasabi::ACL_PRIVATE // Access control (default: private) -)); +use Utopia\Storage\Device\Local +use Utopia\Storage\Device\S3 +use Utopia\Storage\Device\DOSpaces -// Available regions -// Wasabi::US_EAST_1, Wasabi::US_EAST_2, Wasabi::US_WEST_1, Wasabi::US_CENTRAL_1, -// Wasabi::EU_CENTRAL_1, Wasabi::EU_CENTRAL_2, Wasabi::EU_WEST_1, Wasabi::EU_WEST_2, -// Wasabi::AP_NORTHEAST_1, Wasabi::AP_NORTHEAST_2 -``` +// Instantiating local storage +Storage::setDevice('files', new Local('path')); -## Common Operations +// Or you can use AWS S3 storage +Storage::setDevice('files', new S3('path', AWS_ACCESS_KEY, AWS_SECRET_KEY,AWS_BUCKET_NAME, AWS_REGION, AWS_ACL_FLAG)); -All storage adapters provide a consistent API for working with files: +// Or you can use DigitalOcean Spaces storage +Storage::setDevice('files', new DOSpaces('path', DO_SPACES_ACCESS_KEY, DO_SPACES_SECRET_KEY, DO_SPACES_BUCKET_NAME, DO_SPACES_REGION, AWS_ACL_FLAG)); -```php -// Get storage device $device = Storage::getDevice('files'); -// Upload a file -$device->upload('/path/to/local/file.jpg', 'remote/path/file.jpg'); - -// Check if file exists -$exists = $device->exists('remote/path/file.jpg'); +//upload +$device->upload('file.png','path'); -// Get file size -$size = $device->getFileSize('remote/path/file.jpg'); +//delete +$device->delete('path'); -// Get file MIME type -$mime = $device->getFileMimeType('remote/path/file.jpg'); - -// Get file MD5 hash -$hash = $device->getFileHash('remote/path/file.jpg'); - -// Read file contents -$contents = $device->read('remote/path/file.jpg'); - -// Read partial file contents -$chunk = $device->read('remote/path/file.jpg', 0, 1024); // Read first 1KB - -// Multipart/chunked uploads -$device->upload('/local/file.mp4', 'remote/video.mp4', 1, 3); // Part 1 of 3 - -// Create directory -$device->createDirectory('remote/new-directory'); - -// List files in directory -$files = $device->listFiles('remote/directory'); - -// Delete file -$device->delete('remote/path/file.jpg'); - -// Delete directory -$device->deleteDirectory('remote/directory'); - -// Transfer files between storage devices -$sourceDevice = Storage::getDevice('source'); -$targetDevice = Storage::getDevice('target'); - -$sourceDevice->transfer('source/path.jpg', 'target/path.jpg', $targetDevice); ``` -## Adding New Adapters - -For information on adding new storage adapters, see the [Adding New Storage Adapter](https://github.com/utopia-php/storage/blob/master/docs/adding-new-storage-adapter.md) guide. - ## System Requirements -Utopia Storage requires PHP 7.4 or later. We recommend using the latest PHP version whenever possible. - -## Contributing - -For security issues, please email [security@appwrite.io](mailto:security@appwrite.io) instead of posting a public issue in GitHub. - -All code contributions - including those of people having commit access - must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code. +Utopia Framework requires PHP 7.4 or later. We recommend using the latest PHP version whenever possible. -We welcome you to contribute to the Utopia Storage library. For details on how to do this, please refer to our [Contributing Guide](https://github.com/utopia-php/storage/blob/master/CONTRIBUTING.md). +## Copyright and license -## License - -This library is available under the MIT License. - -## Copyright - -``` -Copyright (c) 2019-2025 Appwrite Team -``` +The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) diff --git a/composer.json b/composer.json index 152edfc5..c3b8f2d1 100644 --- a/composer.json +++ b/composer.json @@ -21,16 +21,24 @@ "ext-zstd": "*", "ext-xz": "*", "ext-brotli": "*", + "ext-curl": "*", "ext-lz4": "*", + "ext-simplexml": "*", "ext-snappy": "*", - "php": ">=8.0", - "utopia-php/validators": "0.0.*", - "utopia-php/system": "0.9.*" + "php": ">=8.1", + "utopia-php/system": "0.*.*", + "utopia-php/telemetry": "0.2.*", + "utopia-php/validators": "0.1.*" }, "require-dev": { "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.0.1", "laravel/pint": "1.2.*" }, - "minimum-stability": "stable" + "config": { + "allow-plugins": { + "php-http/discovery": false, + "tbachert/spi": false + } + } } diff --git a/composer.lock b/composer.lock index cad29a41..25c24113 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,1869 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e9019844bb82d49809d4b1b3001a4547", + "content-hash": "c461b7a9538f9b1c1374f981efe3f81b", "packages": [ + { + "name": "brick/math", + "version": "0.14.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.14.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-11-24T14:40:29+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "google/protobuf", + "version": "v4.33.2", + "source": { + "type": "git", + "url": "https://github.com/protocolbuffers/protobuf-php.git", + "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", + "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", + "shasum": "" + }, + "require": { + "php": ">=8.1.0" + }, + "require-dev": { + "phpunit/phpunit": ">=5.0.0 <8.5.27" + }, + "suggest": { + "ext-bcmath": "Need to support JSON deserialization" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Protobuf\\": "src/Google/Protobuf", + "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "proto library for PHP", + "homepage": "https://developers.google.com/protocol-buffers/", + "keywords": [ + "proto" + ], + "support": { + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.2" + }, + "time": "2025-12-05T22:12:22+00:00" + }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "nyholm/psr7-server", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-11-08T09:30:43+00:00" + }, + { + "name": "open-telemetry/api", + "version": "1.7.1", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", + "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", + "shasum": "" + }, + "require": { + "open-telemetry/context": "^1.4", + "php": "^8.1", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.0.8" + }, + "type": "library", + "extra": { + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + }, + "branch-alias": { + "dev-main": "1.8.x-dev" + } + }, + "autoload": { + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2025-10-19T10:49:48+00:00" + }, + { + "name": "open-telemetry/context", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf", + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/polyfill-php82": "^1.26" + }, + "suggest": { + "ext-ffi": "To allow context switching in Fibers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Context implementation for OpenTelemetry PHP.", + "keywords": [ + "Context", + "opentelemetry", + "otel" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2025-09-19T00:05:49+00:00" + }, + { + "name": "open-telemetry/exporter-otlp", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/exporter-otlp.git", + "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/07b02bc71838463f6edcc78d3485c04b48fb263d", + "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d", + "shasum": "" + }, + "require": { + "open-telemetry/api": "^1.0", + "open-telemetry/gen-otlp-protobuf": "^1.1", + "open-telemetry/sdk": "^1.0", + "php": "^8.1", + "php-http/discovery": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "_register.php" + ], + "psr-4": { + "OpenTelemetry\\Contrib\\Otlp\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "OTLP exporter for OpenTelemetry.", + "keywords": [ + "Metrics", + "exporter", + "gRPC", + "http", + "opentelemetry", + "otel", + "otlp", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2025-11-13T08:04:37+00:00" + }, + { + "name": "open-telemetry/gen-otlp-protobuf", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", + "reference": "673af5b06545b513466081884b47ef15a536edde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/673af5b06545b513466081884b47ef15a536edde", + "reference": "673af5b06545b513466081884b47ef15a536edde", + "shasum": "" + }, + "require": { + "google/protobuf": "^3.22 || ^4.0", + "php": "^8.0" + }, + "suggest": { + "ext-protobuf": "For better performance, when dealing with the protobuf format" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opentelemetry\\Proto\\": "Opentelemetry/Proto/", + "GPBMetadata\\Opentelemetry\\": "GPBMetadata/Opentelemetry/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "PHP protobuf files for communication with OpenTelemetry OTLP collectors/servers.", + "keywords": [ + "Metrics", + "apm", + "gRPC", + "logging", + "opentelemetry", + "otel", + "otlp", + "protobuf", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2025-09-17T23:10:12+00:00" + }, + { + "name": "open-telemetry/sdk", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sdk.git", + "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", + "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nyholm/psr7-server": "^1.1", + "open-telemetry/api": "^1.7", + "open-telemetry/context": "^1.4", + "open-telemetry/sem-conv": "^1.0", + "php": "^8.1", + "php-http/discovery": "^1.14", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.0.1|^2.0", + "psr/log": "^1.1|^2.0|^3.0", + "ramsey/uuid": "^3.0 || ^4.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php82": "^1.26", + "tbachert/spi": "^1.0.5" + }, + "suggest": { + "ext-gmp": "To support unlimited number of synchronous metric readers", + "ext-mbstring": "To increase performance of string operations", + "open-telemetry/sdk-configuration": "File-based OpenTelemetry SDK configuration" + }, + "type": "library", + "extra": { + "spi": { + "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig" + ], + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" + ], + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + }, + "branch-alias": { + "dev-main": "1.9.x-dev" + } + }, + "autoload": { + "files": [ + "Common/Util/functions.php", + "Logs/Exporter/_register.php", + "Metrics/MetricExporter/_register.php", + "Propagation/_register.php", + "Trace/SpanExporter/_register.php", + "Common/Dev/Compatibility/_load.php", + "_autoload.php" + ], + "psr-4": { + "OpenTelemetry\\SDK\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "SDK for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "sdk", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2025-11-25T10:59:15+00:00" + }, + { + "name": "open-telemetry/sem-conv", + "version": "1.37.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sem-conv.git", + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1", + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenTelemetry\\SemConv\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Semantic conventions for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "semantic conventions", + "semconv", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2025-09-03T12:08:10+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "8429c78ca35a09f27565311b98101e2826affde0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.2" + }, + "time": "2025-12-14T04:43:48+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616", + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-23T14:50:43+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "75d7043853a42837e68111812f4d964b01e5101c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-29T11:18:49+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php82", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "5d2ed36f7734637dacc025f179698031951b1692" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", + "reference": "5d2ed36f7734637dacc025f179698031951b1692", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "tbachert/spi", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/Nevay/spi.git", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "composer/semver": "^1.0 || ^2.0 || ^3.0", + "php": "^8.1" + }, + "require-dev": { + "composer/composer": "^2.0", + "infection/infection": "^0.27.9", + "phpunit/phpunit": "^10.5", + "psalm/phar": "^5.18" + }, + "type": "composer-plugin", + "extra": { + "class": "Nevay\\SPI\\Composer\\Plugin", + "branch-alias": { + "dev-main": "1.0.x-dev" + }, + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Nevay\\SPI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Service provider loading facility", + "keywords": [ + "service provider" + ], + "support": { + "issues": "https://github.com/Nevay/spi/issues", + "source": "https://github.com/Nevay/spi/tree/v1.0.5" + }, + "time": "2025-06-29T15:42:06+00:00" + }, { "name": "utopia-php/system", "version": "0.9.0", @@ -62,28 +1923,82 @@ }, "time": "2024-10-09T14:44:01+00:00" }, + { + "name": "utopia-php/telemetry", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/telemetry.git", + "reference": "9997ebf59bb77920a7223ad73d834a76b09152c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/9997ebf59bb77920a7223ad73d834a76b09152c3", + "reference": "9997ebf59bb77920a7223ad73d834a76b09152c3", + "shasum": "" + }, + "require": { + "ext-opentelemetry": "*", + "ext-protobuf": "*", + "nyholm/psr7": "1.*", + "open-telemetry/exporter-otlp": "1.*", + "open-telemetry/sdk": "1.*", + "php": ">=8.0", + "symfony/http-client": "7.*" + }, + "require-dev": { + "laravel/pint": "1.*", + "phpbench/phpbench": "1.*", + "phpstan/phpstan": "2.*", + "phpunit/phpunit": "11.*", + "swoole/ide-helper": "6.*" + }, + "suggest": { + "ext-sockets": "Required for the Swoole transport implementation", + "ext-swoole": "Required for the Swoole transport implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Telemetry\\": "src/Telemetry" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "keywords": [ + "framework", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/telemetry/issues", + "source": "https://github.com/utopia-php/telemetry/tree/0.2.0" + }, + "time": "2025-12-17T07:56:38+00:00" + }, { "name": "utopia-php/validators", - "version": "0.0.2", + "version": "0.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/validators.git", - "reference": "894210695c5d35fa248fb65f7fe7237b6ff4fb0b" + "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/validators/zipball/894210695c5d35fa248fb65f7fe7237b6ff4fb0b", - "reference": "894210695c5d35fa248fb65f7fe7237b6ff4fb0b", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/5c57d5b6cf964f8981807c1d3ea8df620c869080", + "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.0" }, "require-dev": { - "ext-xdebug": "*", - "laravel/pint": "^1.2", + "laravel/pint": "1.*", "phpstan/phpstan": "1.*", - "phpunit/phpunit": "^9.5.25" + "phpunit/phpunit": "11.*" }, "type": "library", "autoload": { @@ -104,9 +2019,9 @@ ], "support": { "issues": "https://github.com/utopia-php/validators/issues", - "source": "https://github.com/utopia-php/validators/tree/0.0.2" + "source": "https://github.com/utopia-php/validators/tree/0.1.0" }, - "time": "2025-10-20T21:52:28+00:00" + "time": "2025-11-18T11:05:46+00:00" } ], "packages-dev": [ @@ -338,83 +2253,6 @@ ], "time": "2022-01-17T14:14:24+00:00" }, - { - "name": "composer/semver", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2025-08-20T19:15:30+00:00" - }, { "name": "composer/xdebug-handler", "version": "1.4.6", @@ -914,16 +2752,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.19.4", + "version": "v4.19.5", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" + "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", - "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", + "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", "shasum": "" }, "require": { @@ -938,11 +2776,6 @@ "bin/php-parse" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, "autoload": { "psr-4": { "PhpParser\\": "lib/PhpParser" @@ -964,9 +2797,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.5" }, - "time": "2024-09-29T15:01:53+00:00" + "time": "2025-12-06T11:45:25+00:00" }, { "name": "openlss/lib-array2xml", @@ -1194,16 +3027,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.3", + "version": "5.6.6", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", "shasum": "" }, "require": { @@ -1213,7 +3046,7 @@ "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", "phpstan/phpdoc-parser": "^1.7|^2.0", - "webmozart/assert": "^1.9.1" + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { "mockery/mockery": "~1.3.5 || ~1.6.0", @@ -1252,22 +3085,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" }, - "time": "2025-08-01T19:43:32+00:00" + "time": "2025-12-22T21:13:58+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.10.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { @@ -1310,9 +3143,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2024-11-09T15:12:26+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -1682,16 +3515,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.29", + "version": "9.6.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" + "reference": "945d0b7f346a084ce5549e95289962972c4272e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", - "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/945d0b7f346a084ce5549e95289962972c4272e5", + "reference": "945d0b7f346a084ce5549e95289962972c4272e5", "shasum": "" }, "require": { @@ -1765,7 +3598,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.31" }, "funding": [ { @@ -1774,125 +3607,22 @@ }, { "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2025-09-24T06:29:11+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "type": "github" + }, { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2025-12-06T07:45:52+00:00" }, { "name": "sebastian/cli-parser", @@ -3004,73 +4734,6 @@ ], "time": "2024-11-06T11:30:55+00:00" }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, { "name": "symfony/polyfill-ctype", "version": "v1.33.0", @@ -3321,91 +4984,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-23T08:48:59+00:00" - }, { "name": "symfony/polyfill-php73", "version": "v1.33.0", @@ -3570,101 +5148,18 @@ ], "time": "2025-01-02T08:10:11+00:00" }, - { - "name": "symfony/service-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-04-25T09:37:31+00:00" - }, { "name": "symfony/string", - "version": "v6.4.26", + "version": "v6.4.30", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea" + "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5621f039a71a11c87c106c1c598bdcd04a19aeea", - "reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea", + "url": "https://api.github.com/repos/symfony/string/zipball/50590a057841fa6bf69d12eceffce3465b9e32cb", + "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb", "shasum": "" }, "require": { @@ -3720,7 +5215,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.26" + "source": "https://github.com/symfony/string/tree/v6.4.30" }, "funding": [ { @@ -3740,20 +5235,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:32:46+00:00" + "time": "2025-11-21T18:03:05+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -3782,7 +5277,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -3790,7 +5285,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "vimeo/psalm", @@ -3899,16 +5394,16 @@ }, { "name": "webmozart/assert", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "541057574806f942c94662b817a50f63f7345360" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/541057574806f942c94662b817a50f63f7345360", - "reference": "541057574806f942c94662b817a50f63f7345360", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { @@ -3951,9 +5446,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2025-10-20T12:43:39+00:00" + "time": "2025-10-29T15:56:20+00:00" }, { "name": "webmozart/glob", @@ -4067,10 +5562,12 @@ "ext-zstd": "*", "ext-xz": "*", "ext-brotli": "*", + "ext-curl": "*", "ext-lz4": "*", + "ext-simplexml": "*", "ext-snappy": "*", - "php": ">=8.0" + "php": ">=8.1" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/phpunit.xml b/phpunit.xml index 49774ae9..1eb9dd9d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,6 +9,7 @@ stopOnFailure="false"> + ./tests/Storage/Compression ./tests/Storage/Device/LocalTest.php ./tests/Storage/Validator ./tests/Storage/StorageTest.php diff --git a/src/Storage/Compression/Algorithms/Brotli.php b/src/Storage/Compression/Algorithms/Brotli.php new file mode 100644 index 00000000..e82c49cc --- /dev/null +++ b/src/Storage/Compression/Algorithms/Brotli.php @@ -0,0 +1,104 @@ +level; + } + + /** + * Sets the brotli compression mode to generic. + * + * This is the default mode + */ + public function useGenericMode(): void + { + $this->mode = BROTLI_GENERIC; + } + + /** + * Sets the brotli compression mode to UTF-8 text mode. + * + * Optimizes compression for UTF-8 formatted text + * + * @link https://github.com/kjdev/php-ext-brotli#parameters + */ + public function useTextMode(): void + { + $this->mode = BROTLI_TEXT; + } + + /** + * Sets the brotli compression mode to font mode. + * + * Optimized compression for WOFF 2.0 Fonts + * + * @link https://github.com/kjdev/php-ext-brotli#parameters + */ + public function useFontMode(): void + { + $this->mode = BROTLI_FONT; + } + + /** + * Set the compression level. + * + * Allow values from 0 up to a current max of 11. + * + * @param int $level + * @return void + */ + public function setLevel(int $level): void + { + $min = BROTLI_COMPRESS_LEVEL_MIN; + $max = BROTLI_COMPRESS_LEVEL_MAX; + if ($level < $min || $level > $max) { + throw new \InvalidArgumentException("Level must be between {$min} and {$max}"); + } + $this->level = $level; // $level; + } + + /** + * Compress. + * + * @param string $data + * @return string + */ + public function compress(string $data): string + { + return \brotli_compress($data, $this->getLevel(), $this->mode); + } + + /** + * Decompress. + * + * @param string $data + * @return string + */ + public function decompress(string $data): string + { + return \brotli_uncompress($data); + } +} diff --git a/src/Storage/Compression/Algorithms/GZIP.php b/src/Storage/Compression/Algorithms/GZIP.php new file mode 100644 index 00000000..e047a266 --- /dev/null +++ b/src/Storage/Compression/Algorithms/GZIP.php @@ -0,0 +1,43 @@ +level = $level; + } + + /** + * Get the compression level. + * + * @return int + */ + public function getLevel(): int + { + return $this->level; + } + + /** + * Set the compression level. + * + * Allow values from 0 up to a current max of 12. + * + * @param int $level + * @return void + */ + public function setLevel(int $level): void + { + if ($level < 0 || $level > 12) { + throw new \InvalidArgumentException('Level must be between 0 and 12'); + } + $this->level = $level; + } + + /** + * Get the name of the algorithm. + * + * @return string + */ + public function getName(): string + { + return 'lz4'; + } + + /** + * Compress. + * + * @param string $data + * @return string + */ + public function compress(string $data): string + { + return \lz4_compress($data, $this->level); + } + + /** + * Decompress. + * + * @param string $data + * @return string + */ + public function decompress(string $data): string + { + return \lz4_uncompress($data); + } +} diff --git a/src/Storage/Compression/Algorithms/Snappy.php b/src/Storage/Compression/Algorithms/Snappy.php new file mode 100644 index 00000000..32af571f --- /dev/null +++ b/src/Storage/Compression/Algorithms/Snappy.php @@ -0,0 +1,38 @@ += 20 should be used with caution, as they require more memory. + * + * Default value is 3. + */ + protected int $level = 3; + + public function __construct(int $level = 3) + { + $this->level = $level; + } + + /** + * Get the compression level. + * + * @return int + */ + public function getLevel(): int + { + return $this->level; + } + + /** + * Set the compression level. + * + * Allow values from 1 up to a current max of 22. + * + * @param int $level + * @return void + */ + public function setLevel(int $level): void + { + if ($level < 1 || $level > 22) { + throw new \InvalidArgumentException('Level must be between 1 and 22'); + } + $this->level = $level; + } + + /** + * Get the name of the algorithm. + * + * @return string + */ + public function getName(): string + { + return 'zstd'; + } + + /** + * Compress. + * + * @param string $data + * @return string + */ + public function compress(string $data): string + { + return \zstd_compress($data, $this->level); + } + + /** + * Decompress. + * + * @param string $data + * @return string + */ + public function decompress(string $data): string + { + return \zstd_uncompress($data); + } +} diff --git a/src/Storage/Compression/Compression.php b/src/Storage/Compression/Compression.php new file mode 100644 index 00000000..57b57d90 --- /dev/null +++ b/src/Storage/Compression/Compression.php @@ -0,0 +1,39 @@ +setTelemetry($telemetry); + } + /** * Set Transfer Chunk Size * @@ -64,6 +74,15 @@ abstract public function getType(): string; */ abstract public function getDescription(): string; + public function setTelemetry(Telemetry $telemetry): void + { + $this->storageOperationTelemetry = $telemetry->createHistogram( + 'storage.operation', + 's', + advisory: ['ExplicitBucketBoundaries' => [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]] + ); + } + /** * Get Root. * @@ -82,7 +101,7 @@ abstract public function getRoot(): string; * @param string $prefix * @return string */ - abstract public function getPath(string $filename, string $prefix = null): string; + abstract public function getPath(string $filename, ?string $prefix = null): string; /** * Upload. @@ -136,7 +155,7 @@ abstract public function abort(string $path, string $extra = ''): bool; * @param int $length * @return string */ - abstract public function read(string $path, int $offset = 0, int $length = null): string; + abstract public function read(string $path, int $offset = 0, ?int $length = null): string; /** * Transfer diff --git a/src/Storage/Device/AWS.php b/src/Storage/Device/AWS.php new file mode 100644 index 00000000..3d7f8692 --- /dev/null +++ b/src/Storage/Device/AWS.php @@ -0,0 +1,106 @@ + $bucket.'.s3.'.$region.'.amazonaws.cn', + default => $bucket.'.s3.'.$region.'.amazonaws.com' + }; + parent::__construct($root, $accessKey, $secretKey, $host, $region, $acl); + } + + /** + * @return string + */ + public function getName(): string + { + return 'AWS S3 Storage'; + } + + /** + * @return string + */ + public function getType(): string + { + return Storage::DEVICE_AWS_S3; + } + + /** + * @return string + */ + public function getDescription(): string + { + return 'S3 Bucket Storage drive for AWS'; + } +} diff --git a/src/Storage/Device/Backblaze.php b/src/Storage/Device/Backblaze.php index 7420098e..32ad1589 100644 --- a/src/Storage/Device/Backblaze.php +++ b/src/Storage/Device/Backblaze.php @@ -34,8 +34,8 @@ class Backblaze extends S3 */ public function __construct(string $root, string $accessKey, string $secretKey, string $bucket, string $region = self::US_WEST_004, string $acl = self::ACL_PRIVATE) { - parent::__construct($root, $accessKey, $secretKey, $bucket, $region, $acl); - $this->headers['host'] = $bucket.'.'.'s3'.'.'.$region.'.backblazeb2.com'; + $host = $bucket.'.'.'s3'.'.'.$region.'.backblazeb2.com'; + parent::__construct($root, $accessKey, $secretKey, $host, $region, $acl); } /** diff --git a/src/Storage/Device/DOSpaces.php b/src/Storage/Device/DOSpaces.php index 853c2dfc..82914b8d 100644 --- a/src/Storage/Device/DOSpaces.php +++ b/src/Storage/Device/DOSpaces.php @@ -33,8 +33,8 @@ class DOSpaces extends S3 */ public function __construct(string $root, string $accessKey, string $secretKey, string $bucket, string $region = self::NYC3, string $acl = self::ACL_PRIVATE) { - parent::__construct($root, $accessKey, $secretKey, $bucket, $region, $acl); - $this->headers['host'] = $bucket.'.'.$region.'.digitaloceanspaces.com'; + $host = $bucket.'.'.$region.'.digitaloceanspaces.com'; + parent::__construct($root, $accessKey, $secretKey, $host, $region, $acl); } /** diff --git a/src/Storage/Device/Linode.php b/src/Storage/Device/Linode.php index fd8c79b8..38a665d3 100644 --- a/src/Storage/Device/Linode.php +++ b/src/Storage/Device/Linode.php @@ -29,8 +29,8 @@ class Linode extends S3 */ public function __construct(string $root, string $accessKey, string $secretKey, string $bucket, string $region = self::EU_CENTRAL_1, string $acl = self::ACL_PRIVATE) { - parent::__construct($root, $accessKey, $secretKey, $bucket, $region, $acl); - $this->headers['host'] = $bucket.'.'.$region.'.'.'linodeobjects.com'; + $host = $bucket.'.'.$region.'.'.'linodeobjects.com'; + parent::__construct($root, $accessKey, $secretKey, $host, $region, $acl); } /** diff --git a/src/Storage/Device/Local.php b/src/Storage/Device/Local.php index a092c1e6..0d4018b5 100644 --- a/src/Storage/Device/Local.php +++ b/src/Storage/Device/Local.php @@ -4,6 +4,7 @@ use Exception; use Utopia\Storage\Device; +use Utopia\Storage\Exception\NotFoundException; use Utopia\Storage\Storage; class Local extends Device @@ -20,6 +21,7 @@ class Local extends Device */ public function __construct(string $root = '') { + parent::__construct(); $this->root = $root; } @@ -60,7 +62,7 @@ public function getRoot(): string * @param string|null $prefix * @return string */ - public function getPath(string $filename, string $prefix = null): string + public function getPath(string $filename, ?string $prefix = null): string { return $this->getAbsolutePath($this->getRoot().DIRECTORY_SEPARATOR.$filename); } @@ -268,10 +270,10 @@ public function abort(string $path, string $extra = ''): bool * * @throws Exception */ - public function read(string $path, int $offset = 0, int $length = null): string + public function read(string $path, int $offset = 0, ?int $length = null): string { if (! $this->exists($path)) { - throw new Exception('File Not Found'); + throw new NotFoundException('File not found'); } return \file_get_contents($path, use_include_path: false, context: null, offset: $offset, length: $length); diff --git a/src/Storage/Device/S3.php b/src/Storage/Device/S3.php index c21bb9cb..429543c6 100644 --- a/src/Storage/Device/S3.php +++ b/src/Storage/Device/S3.php @@ -4,6 +4,7 @@ use Exception; use Utopia\Storage\Device; +use Utopia\Storage\Exception\NotFoundException; use Utopia\Storage\Storage; class S3 extends Device @@ -34,61 +35,6 @@ class S3 extends Device const HTTP_VERSION_1_0 = CURL_HTTP_VERSION_1_0; - /** - * AWS Regions constants - */ - const US_EAST_1 = 'us-east-1'; - - const US_EAST_2 = 'us-east-2'; - - const US_WEST_1 = 'us-west-1'; - - const US_WEST_2 = 'us-west-2'; - - const AF_SOUTH_1 = 'af-south-1'; - - const AP_EAST_1 = 'ap-east-1'; - - const AP_SOUTH_1 = 'ap-south-1'; - - const AP_NORTHEAST_3 = 'ap-northeast-3'; - - const AP_NORTHEAST_2 = 'ap-northeast-2'; - - const AP_NORTHEAST_1 = 'ap-northeast-1'; - - const AP_SOUTHEAST_1 = 'ap-southeast-1'; - - const AP_SOUTHEAST_2 = 'ap-southeast-2'; - - const CA_CENTRAL_1 = 'ca-central-1'; - - const EU_CENTRAL_1 = 'eu-central-1'; - - const EU_WEST_1 = 'eu-west-1'; - - const EU_SOUTH_1 = 'eu-south-1'; - - const EU_WEST_2 = 'eu-west-2'; - - const EU_WEST_3 = 'eu-west-3'; - - const EU_NORTH_1 = 'eu-north-1'; - - const SA_EAST_1 = 'eu-north-1'; - - const CN_NORTH_1 = 'cn-north-1'; - - const CN_NORTH_4 = 'cn-north-4'; - - const CN_NORTHWEST_1 = 'cn-northwest-1'; - - const ME_SOUTH_1 = 'me-south-1'; - - const US_GOV_EAST_1 = 'us-gov-east-1'; - - const US_GOV_WEST_1 = 'us-gov-west-1'; - /** * AWS ACL Flag constants */ @@ -116,11 +62,6 @@ class S3 extends Device */ protected string $secretKey; - /** - * @var string - */ - protected string $bucket; - /** * @var string */ @@ -146,6 +87,8 @@ class S3 extends Device 'content-type' => '', ]; + protected string $fqdn; + /** * @var array */ @@ -164,30 +107,27 @@ class S3 extends Device * @param string $root * @param string $accessKey * @param string $secretKey - * @param string $bucket * @param string $region * @param string $acl */ - public function __construct(string $root, string $accessKey, string $secretKey, string $bucket, string $region = self::US_EAST_1, string $acl = self::ACL_PRIVATE, $endpointUrl = '') + public function __construct(string $root, string $accessKey, string $secretKey, string $host, string $region, string $acl = self::ACL_PRIVATE) { + parent::__construct(); + $this->accessKey = $accessKey; $this->secretKey = $secretKey; - $this->bucket = $bucket; $this->region = $region; $this->root = $root; $this->acl = $acl; $this->amzHeaders = []; - if (! empty($endpointUrl)) { - $host = $bucket.'.'.$endpointUrl; + if (str_starts_with($host, 'http://') || str_starts_with($host, 'https://')) { + $this->fqdn = $host; + $this->headers['host'] = str_replace(['http://', 'https://'], '', $host); } else { - $host = match ($region) { - self::CN_NORTH_1, self::CN_NORTH_4, self::CN_NORTHWEST_1 => $bucket.'.s3.'.$region.'.amazonaws.cn', - default => $bucket.'.s3.'.$region.'.amazonaws.com' - }; + $this->fqdn = 'https://'.$host; + $this->headers['host'] = $host; } - - $this->headers['host'] = $host; } /** @@ -211,7 +151,7 @@ public function getType(): string */ public function getDescription(): string { - return 'S3 Bucket Storage drive for AWS or on premise solution'; + return 'S3 Storage drive for generic S3-compatible provider'; } /** @@ -227,7 +167,7 @@ public function getRoot(): string * @param string|null $prefix * @return string */ - public function getPath(string $filename, string $prefix = null): string + public function getPath(string $filename, ?string $prefix = null): string { return $this->getRoot().DIRECTORY_SEPARATOR.$filename; } @@ -345,7 +285,7 @@ public function transfer(string $path, string $destination, Device $device): boo try { $response = $this->getInfo($path); } catch (\Throwable $e) { - throw new Exception('File not found'); + throw new NotFoundException('File not found'); } $size = (int) ($response['content-length'] ?? 0); $contentType = $response['content-type'] ?? ''; @@ -386,7 +326,7 @@ protected function createMultipartUpload(string $path, string $contentType): str unset($this->amzHeaders['x-amz-content-sha256']); $this->headers['content-type'] = $contentType; $this->amzHeaders['x-amz-acl'] = $this->acl; - $response = $this->call(self::METHOD_POST, $uri, '', ['uploads' => '']); + $response = $this->call('s3:createMultipartUpload', self::METHOD_POST, $uri, '', ['uploads' => '']); return $response->body['UploadId']; } @@ -411,7 +351,7 @@ protected function uploadPart(string $data, string $path, string $contentType, i $this->amzHeaders['x-amz-content-sha256'] = \hash('sha256', $data); unset($this->amzHeaders['x-amz-acl']); // ACL header is not allowed in parts, only createMultipartUpload accepts this header. - $response = $this->call(self::METHOD_PUT, $uri, $data, [ + $response = $this->call('s3:uploadPart', self::METHOD_PUT, $uri, $data, [ 'partNumber' => $chunk, 'uploadId' => $uploadId, ]); @@ -441,7 +381,7 @@ protected function completeMultipartUpload(string $path, string $uploadId, array $this->amzHeaders['x-amz-content-sha256'] = \hash('sha256', $body); $this->headers['content-md5'] = \base64_encode(md5($body, true)); - $this->call(self::METHOD_POST, $uri, $body, ['uploadId' => $uploadId]); + $this->call('s3:completeMultipartUpload', self::METHOD_POST, $uri, $body, ['uploadId' => $uploadId]); return true; } @@ -460,7 +400,7 @@ public function abort(string $path, string $extra = ''): bool $uri = $path !== '' ? '/'.\str_replace(['%2F', '%3F'], ['/', '?'], \rawurlencode($path)) : '/'; unset($this->headers['content-type']); $this->headers['content-md5'] = \base64_encode(md5('', true)); - $this->call(self::METHOD_DELETE, $uri, '', ['uploadId' => $extra]); + $this->call('s3:abort', self::METHOD_DELETE, $uri, '', ['uploadId' => $extra]); return true; } @@ -470,12 +410,12 @@ public function abort(string $path, string $extra = ''): bool * * @param string $path * @param int offset - * @param int length + * @param int|null length * @return string * * @throws \Exception */ - public function read(string $path, int $offset = 0, int $length = null): string + public function read(string $path, int $offset = 0, ?int $length = null): string { unset($this->amzHeaders['x-amz-acl']); unset($this->amzHeaders['x-amz-content-sha256']); @@ -486,7 +426,7 @@ public function read(string $path, int $offset = 0, int $length = null): string $end = $offset + $length - 1; $this->headers['range'] = "bytes=$offset-$end"; } - $response = $this->call(self::METHOD_GET, $uri, decode: false); + $response = $this->call('s3:read', self::METHOD_GET, $uri, decode: false); return $response->body; } @@ -509,7 +449,7 @@ public function write(string $path, string $data, string $contentType = ''): boo $this->amzHeaders['x-amz-content-sha256'] = \hash('sha256', $data); $this->amzHeaders['x-amz-acl'] = $this->acl; - $this->call(self::METHOD_PUT, $uri, $data); + $this->call('s3:write', self::METHOD_PUT, $uri, $data); return true; } @@ -532,7 +472,7 @@ public function delete(string $path, bool $recursive = false): bool unset($this->amzHeaders['x-amz-acl']); unset($this->amzHeaders['x-amz-content-sha256']); $this->headers['content-md5'] = \base64_encode(md5('', true)); - $this->call(self::METHOD_DELETE, $uri); + $this->call('s3:delete', self::METHOD_DELETE, $uri); return true; } @@ -571,7 +511,7 @@ protected function listObjects(string $prefix = '', int $maxKeys = self::MAX_PAG $parameters['continuation-token'] = $continuationToken; } - $response = $this->call(self::METHOD_GET, $uri, '', $parameters); + $response = $this->call('s3:list', self::METHOD_GET, $uri, '', $parameters); return $response->body; } @@ -609,7 +549,7 @@ public function deletePath(string $path): bool $body .= ''; $this->amzHeaders['x-amz-content-sha256'] = \hash('sha256', $body); $this->headers['content-md5'] = \base64_encode(md5($body, true)); - $this->call(self::METHOD_POST, $uri, $body, ['delete' => '']); + $this->call('s3:deletePath', self::METHOD_POST, $uri, $body, ['delete' => '']); } while (! empty($continuationToken)); return true; @@ -771,7 +711,7 @@ private function getInfo(string $path): array unset($this->amzHeaders['x-amz-content-sha256']); $this->headers['content-md5'] = \base64_encode(md5('', true)); $uri = $path !== '' ? '/'.\str_replace('%2F', '/', \rawurlencode($path)) : '/'; - $response = $this->call(self::METHOD_HEAD, $uri); + $response = $this->call('s3:info', self::METHOD_HEAD, $uri); return $response->headers; } @@ -857,6 +797,7 @@ private function getSignatureV4(string $method, string $uri, array $parameters = /** * Get the S3 response * + * @param string $operation * @param string $method * @param string $uri * @param string $data @@ -866,10 +807,12 @@ private function getSignatureV4(string $method, string $uri, array $parameters = * * @throws \Exception */ - protected function call(string $method, string $uri, string $data = '', array $parameters = [], bool $decode = true) + protected function call(string $operation, string $method, string $uri, string $data = '', array $parameters = [], bool $decode = true) { + $startTime = microtime(true); + $uri = $this->getAbsolutePath($uri); - $url = 'https://'.$this->headers['host'].$uri.'?'.\http_build_query($parameters, '', '&', PHP_QUERY_RFC3986); + $url = $this->fqdn.$uri.'?'.\http_build_query($parameters, '', '&', PHP_QUERY_RFC3986); $response = new \stdClass; $response->body = ''; $response->headers = []; @@ -948,30 +891,71 @@ protected function call(string $method, string $uri, string $data = '', array $p $response->code = \curl_getinfo($curl, CURLINFO_HTTP_CODE); $attempt = 0; - while ($attempt < self::$retryAttempts && $response->code === 503) { + while ($attempt < self::$retryAttempts && $response->code >= 500) { usleep(self::$retryDelay * 1000); $attempt++; $result = \curl_exec($curl); $response->code = \curl_getinfo($curl, CURLINFO_HTTP_CODE); } - if (! $result) { - throw new Exception(\curl_error($curl)); - } + try { + if (! $result) { + throw new Exception(\curl_error($curl)); + } - if ($response->code >= 400) { - throw new Exception($response->body, $response->code); + if ($response->code >= 400) { + $this->parseAndThrowS3Error($response->body, $response->code); + } + + // Parse body into XML + if ($decode && ((isset($response->headers['content-type']) && $response->headers['content-type'] == 'application/xml') || (str_starts_with($response->body, 'headers['content-type'] ?? '') !== 'image/svg+xml'))) { + $response->body = \simplexml_load_string($response->body); + $response->body = json_decode(json_encode($response->body), true); + } + + return $response; + } finally { + \curl_close($curl); + + $this->storageOperationTelemetry->record( + microtime(true) - $startTime, + [ + 'storage' => $this->getType(), + 'operation' => $operation, + 'attempts' => $attempt, + ] + ); } + } - \curl_close($curl); + /** + * Parse S3 XML error response and throw appropriate exception + * + * @param string $errorBody The error response body + * @param int $statusCode The HTTP status code + * + * @throws NotFoundException When the error is NoSuchKey + * @throws Exception For other S3 errors + */ + private function parseAndThrowS3Error(string $errorBody, int $statusCode): void + { + if (str_starts_with($errorBody, 'Code ?? ''); + $errorMessage = (string) ($xml->Message ?? ''); - // Parse body into XML - if ($decode && ((isset($response->headers['content-type']) && $response->headers['content-type'] == 'application/xml') || (str_starts_with($response->body, 'headers['content-type'] ?? '') !== 'image/svg+xml'))) { - $response->body = \simplexml_load_string($response->body); - $response->body = json_decode(json_encode($response->body), true); + if ($errorCode === 'NoSuchKey') { + throw new NotFoundException($errorMessage ?: 'File not found', $statusCode); + } + } catch (NotFoundException $e) { + throw $e; + } catch (\Throwable $e) { + // If XML parsing fails, fall through to original error + } } - return $response; + throw new Exception($errorBody, $statusCode); } /** @@ -983,7 +967,7 @@ protected function call(string $method, string $uri, string $data = '', array $p * @param string $b String B * @return int */ - private function sortMetaHeadersCmp($a, $b) + protected function sortMetaHeadersCmp(string $a, string $b): int { $lenA = \strlen($a); $lenB = \strlen($b); diff --git a/src/Storage/Device/Telemetry.php b/src/Storage/Device/Telemetry.php new file mode 100644 index 00000000..206aea85 --- /dev/null +++ b/src/Storage/Device/Telemetry.php @@ -0,0 +1,146 @@ +underlying->setTelemetry($telemetry); + } + + private function measure(string $method, &...$args): mixed + { + $start = microtime(true); + try { + return $this->underlying->{$method}(...$args); + } finally { + $this->storageOperationTelemetry->record( + microtime(true) - $start, + [ + 'storage' => $this->underlying->getType(), + 'operation' => "device:$method", + ] + ); + } + } + + public function getName(): string + { + return $this->underlying->getName(); + } + + public function getType(): string + { + return $this->underlying->getType(); + } + + public function getDescription(): string + { + return $this->underlying->getDescription(); + } + + public function getRoot(): string + { + return $this->underlying->getRoot(); + } + + public function getPath(string $filename, ?string $prefix = null): string + { + return $this->measure(__FUNCTION__, $filename, $prefix); + } + + public function upload(string $source, string $path, int $chunk = 1, int $chunks = 1, array &$metadata = []): int + { + return $this->measure(__FUNCTION__, $source, $path, $chunk, $chunks, $metadata); + } + + public function uploadData(string $data, string $path, string $contentType, int $chunk = 1, int $chunks = 1, array &$metadata = []): int + { + return $this->measure(__FUNCTION__, $data, $path, $contentType, $chunk, $chunks, $metadata); + } + + public function abort(string $path, string $extra = ''): bool + { + return $this->measure(__FUNCTION__, $path, $extra); + } + + public function read(string $path, int $offset = 0, ?int $length = null): string + { + return $this->measure(__FUNCTION__, $path, $offset, $length); + } + + public function transfer(string $path, string $destination, Device $device): bool + { + return $this->measure(__FUNCTION__, $path, $destination, $device); + } + + public function write(string $path, string $data, string $contentType): bool + { + return $this->measure(__FUNCTION__, $path, $data, $contentType); + } + + public function delete(string $path, bool $recursive = false): bool + { + return $this->measure(__FUNCTION__, $path, $recursive); + } + + public function deletePath(string $path): bool + { + return $this->measure(__FUNCTION__, $path); + } + + public function exists(string $path): bool + { + return $this->measure(__FUNCTION__, $path); + } + + public function getFileSize(string $path): int + { + return $this->measure(__FUNCTION__, $path); + } + + public function getFileMimeType(string $path): string + { + return $this->measure(__FUNCTION__, $path); + } + + public function getFileHash(string $path): string + { + return $this->measure(__FUNCTION__, $path); + } + + public function createDirectory(string $path): bool + { + return $this->measure(__FUNCTION__, $path); + } + + public function getDirectorySize(string $path): int + { + return $this->measure(__FUNCTION__, $path); + } + + public function getPartitionFreeSpace(): float + { + return $this->measure(__FUNCTION__); + } + + public function getPartitionTotalSpace(): float + { + return $this->measure(__FUNCTION__); + } + + public function getFiles(string $dir, int $max = self::MAX_PAGE_SIZE, string $continuationToken = ''): array + { + return $this->measure(__FUNCTION__, $dir, $max, $continuationToken); + } +} diff --git a/src/Storage/Device/Wasabi.php b/src/Storage/Device/Wasabi.php index dbde5bcb..ec959641 100644 --- a/src/Storage/Device/Wasabi.php +++ b/src/Storage/Device/Wasabi.php @@ -41,8 +41,8 @@ class Wasabi extends S3 */ public function __construct(string $root, string $accessKey, string $secretKey, string $bucket, string $region = self::EU_CENTRAL_1, string $acl = self::ACL_PRIVATE) { - parent::__construct($root, $accessKey, $secretKey, $bucket, $region, $acl); - $this->headers['host'] = $bucket.'.'.'s3'.'.'.$region.'.'.'wasabisys'.'.'.'com'; + $host = $bucket.'.'.'s3'.'.'.$region.'.'.'wasabisys'.'.'.'com'; + parent::__construct($root, $accessKey, $secretKey, $host, $region, $acl); } /** diff --git a/src/Storage/Exception/NotFoundException.php b/src/Storage/Exception/NotFoundException.php new file mode 100644 index 00000000..d09ca44a --- /dev/null +++ b/src/Storage/Exception/NotFoundException.php @@ -0,0 +1,9 @@ +object = new Brotli(); + } + + public function tearDown(): void + { + } + + public function testName() + { + $this->assertEquals($this->object->getName(), 'brotli'); + } + + public function testErrorsWhenSettingLevel() + { + $this->expectException(InvalidArgumentException::class); + $this->object->setLevel(-1); + } + + public function testCompressDecompressWithText() + { + $demo = 'This is a demo string'; + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 21); + $this->assertEquals($dataSize, 25); + + $this->assertEquals($this->object->decompress($data), $demo); + } + + public function testCompressDecompressWithLargeText() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/lorem.txt'); + $demoSize = mb_strlen($demo, '8bit'); + + $this->object->setLevel(8); + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 386795); + $this->assertEquals($dataSize, 33128); + + $this->assertGreaterThan($dataSize, $demoSize); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 386795); + $this->assertEquals($data, $demo); + } + + public function testCompressDecompressWithJPGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); + $demoSize = mb_strlen($demo, '8bit'); + + $this->object->setLevel(8); + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 599639); + // brotli is not the best for images + $this->assertEquals($dataSize, 599644); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 599639); + } + + public function testCompressDecompressWithPNGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); + $demoSize = mb_strlen($demo, '8bit'); + + $this->object->setLevel(8); + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 3038056); + // brotli is not the best for images + $this->assertEquals($dataSize, 3038068); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 3038056); + } +} diff --git a/tests/Storage/Compression/Algorithms/GZIPTest.php b/tests/Storage/Compression/Algorithms/GZIPTest.php new file mode 100644 index 00000000..1f5a8446 --- /dev/null +++ b/tests/Storage/Compression/Algorithms/GZIPTest.php @@ -0,0 +1,99 @@ +object = new GZIP(); + } + + public function tearDown(): void + { + } + + public function testName() + { + $this->assertEquals($this->object->getName(), 'gzip'); + } + + public function testCompressDecompressWithText() + { + $demo = 'This is a demo string'; + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 21); + $this->assertEquals($dataSize, 39); + + $this->assertEquals($this->object->decompress($data), $demo); + } + + public function testCompressDecompressWithLargeText() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/lorem.txt'); + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 386795); + $this->assertEquals($dataSize, 44444); + + $this->assertGreaterThan($dataSize, $demoSize); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 386795); + } + + public function testCompressDecompressWithJPGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 599639); + $this->assertEquals($dataSize, 599107); + + $this->assertGreaterThan($dataSize, $demoSize); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 599639); + } + + public function testCompressDecompressWithPNGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 3038056); + $this->assertEquals($dataSize, 3029202); + + $this->assertGreaterThan($dataSize, $demoSize); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 3038056); + } +} diff --git a/tests/Storage/Compression/Algorithms/LZ4Test.php b/tests/Storage/Compression/Algorithms/LZ4Test.php new file mode 100644 index 00000000..9d66ee85 --- /dev/null +++ b/tests/Storage/Compression/Algorithms/LZ4Test.php @@ -0,0 +1,77 @@ +object = new LZ4(); + } + + public function tearDown(): void + { + } + + public function testName() + { + $this->assertEquals($this->object->getName(), 'lz4'); + } + + public function testCompressDecompressWithText() + { + $demo = 'This is a demo string'; + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(21, $demoSize); + $this->assertEquals(27, $dataSize); + + $this->assertEquals($demo, $this->object->decompress($data)); + } + + public function testCompressDecompressWithJPGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(599639, $demoSize); + $this->assertEquals(601828, $dataSize); + + $this->assertGreaterThan($demoSize, $dataSize); + + $data = $this->object->decompress($data); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(599639, $dataSize); + } + + public function testCompressDecompressWithPNGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(3038056, $demoSize); + $this->assertEquals(3049975, $dataSize); + + $this->assertGreaterThan($demoSize, $dataSize); + + $data = $this->object->decompress($data); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(3038056, $dataSize); + } +} diff --git a/tests/Storage/Compression/Algorithms/SnappyTest.php b/tests/Storage/Compression/Algorithms/SnappyTest.php new file mode 100644 index 00000000..3a752e92 --- /dev/null +++ b/tests/Storage/Compression/Algorithms/SnappyTest.php @@ -0,0 +1,81 @@ +object = new Snappy(); + } + + public function tearDown(): void + { + } + + public function testName() + { + $this->assertEquals($this->object->getName(), 'snappy'); + } + + public function testCompressDecompressWithText() + { + $demo = 'This is a demo string'; + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(21, $demoSize); + $this->assertEquals(23, $dataSize); + + $this->assertEquals($this->object->decompress($data), $demo); + } + + public function testCompressDecompressWithJPGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(599639, $demoSize); + $this->assertEquals(599504, $dataSize); + + $this->assertGreaterThan($dataSize, $demoSize); + + $data = $this->object->decompress($data); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(599639, $dataSize); + } + + public function testCompressDecompressWithPNGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(3038056, $demoSize); + $this->assertEquals(3038200, $dataSize); + + $this->assertGreaterThan($demoSize, $dataSize); + + $data = $this->object->decompress($data); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(3038056, $dataSize); + } +} diff --git a/tests/Storage/Compression/Algorithms/XZTest.php b/tests/Storage/Compression/Algorithms/XZTest.php new file mode 100644 index 00000000..2b55ee01 --- /dev/null +++ b/tests/Storage/Compression/Algorithms/XZTest.php @@ -0,0 +1,77 @@ +object = new XZ(); + } + + public function tearDown(): void + { + } + + public function testName() + { + $this->assertEquals($this->object->getName(), 'xz'); + } + + public function testCompressDecompressWithText() + { + $demo = 'This is a demo string'; + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 21); + $this->assertEquals($dataSize, 80); + + $this->assertEquals($this->object->decompress($data), $demo); + } + + public function testCompressDecompressWithJPGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 599639); + $this->assertEquals($dataSize, 599432); + + $this->assertGreaterThan($dataSize, $demoSize); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 599639); + } + + public function testCompressDecompressWithPNGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 3038056); + $this->assertEquals($dataSize, 2981000); + + $this->assertGreaterThan($dataSize, $demoSize); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 3038056); + } +} diff --git a/tests/Storage/Compression/Algorithms/ZstdTest.php b/tests/Storage/Compression/Algorithms/ZstdTest.php new file mode 100644 index 00000000..488cd290 --- /dev/null +++ b/tests/Storage/Compression/Algorithms/ZstdTest.php @@ -0,0 +1,96 @@ +object = new Zstd(); + } + + public function tearDown(): void + { + } + + public function testName() + { + $this->assertEquals($this->object->getName(), 'zstd'); + } + + public function testCompressDecompressWithText() + { + $demo = 'This is a demo string'; + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(21, $demoSize); + $this->assertEquals(30, $dataSize); + + $this->assertEquals($demo, $this->object->decompress($data)); + } + + public function testCompressDecompressWithLargeText() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/lorem.txt'); + $demoSize = mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($demoSize, 386795); + $this->assertEquals($dataSize, 56324); + + $this->assertGreaterThan($dataSize, $demoSize); + + $data = $this->object->decompress($data); + $dataSize = mb_strlen($data, '8bit'); + + $this->assertEquals($dataSize, 386795); + } + + public function testCompressDecompressWithJPGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(599639, $demoSize); + $this->assertEquals(599663, $dataSize); + + $this->assertGreaterThan($demoSize, $dataSize); + + $data = $this->object->decompress($data); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(599639, $dataSize); + } + + public function testCompressDecompressWithPNGImage() + { + $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); + $demoSize = \mb_strlen($demo, '8bit'); + + $data = $this->object->compress($demo); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(3038056, $demoSize); + $this->assertEquals(3038138, $dataSize); + + $this->assertGreaterThan($demoSize, $dataSize); + + $data = $this->object->decompress($data); + $dataSize = \mb_strlen($data, '8bit'); + + $this->assertEquals(3038056, $dataSize); + } +} diff --git a/tests/Storage/Device/S3Test.php b/tests/Storage/Device/AWSTest.php similarity index 68% rename from tests/Storage/Device/S3Test.php rename to tests/Storage/Device/AWSTest.php index 2b0255c8..84028299 100644 --- a/tests/Storage/Device/S3Test.php +++ b/tests/Storage/Device/AWSTest.php @@ -2,10 +2,10 @@ namespace Utopia\Tests\Storage\Device; -use Utopia\Storage\Device\S3; +use Utopia\Storage\Device\AWS; use Utopia\Tests\Storage\S3Base; -class S3Test extends S3Base +class AWSTest extends S3Base { protected function init(): void { @@ -14,7 +14,7 @@ protected function init(): void $secret = $_SERVER['S3_SECRET'] ?? ''; $bucket = 'utopia-storage-test'; - $this->object = new S3($this->root, $key, $secret, $bucket, S3::EU_CENTRAL_1, S3::ACL_PRIVATE); + $this->object = new AWS($this->root, $key, $secret, $bucket, AWS::EU_CENTRAL_1, AWS::ACL_PRIVATE); } /** @@ -22,7 +22,7 @@ protected function init(): void */ protected function getAdapterName(): string { - return 'S3 Storage'; + return 'AWS S3 Storage'; } protected function getAdapterType(): string @@ -32,6 +32,6 @@ protected function getAdapterType(): string protected function getAdapterDescription(): string { - return 'S3 Bucket Storage drive for AWS or on premise solution'; + return 'S3 Bucket Storage drive for AWS'; } } diff --git a/tests/Storage/Device/LocalTest.php b/tests/Storage/Device/LocalTest.php index 544f0db9..b9e54759 100644 --- a/tests/Storage/Device/LocalTest.php +++ b/tests/Storage/Device/LocalTest.php @@ -3,8 +3,9 @@ namespace Utopia\Tests\Storage\Device; use PHPUnit\Framework\TestCase; +use Utopia\Storage\Device\AWS; use Utopia\Storage\Device\Local; -use Utopia\Storage\Device\S3; +use Utopia\Storage\Exception\NotFoundException; class LocalTest extends TestCase { @@ -75,6 +76,12 @@ public function testRead() $this->object->delete($this->object->getPath('text-for-read.txt')); } + public function testReadNonExistentFile() + { + $this->expectException(NotFoundException::class); + $this->object->read($this->object->getPath('non-existent-file.txt')); + } + public function testFileExists() { $this->assertEquals($this->object->write($this->object->getPath('text-for-test-exists.txt'), 'Hello World'), true); @@ -325,7 +332,7 @@ public function testTransferLarge($path) $secret = $_SERVER['S3_SECRET'] ?? ''; $bucket = 'utopia-storage-test'; - $device = new S3('/root', $key, $secret, $bucket, S3::EU_CENTRAL_1, S3::ACL_PRIVATE); + $device = new AWS('/root', $key, $secret, $bucket, AWS::EU_CENTRAL_1, AWS::ACL_PRIVATE); $destination = $device->getPath('largefile.mp4'); $this->assertTrue($this->object->transfer($path, $destination, $device)); @@ -344,7 +351,7 @@ public function testTransferSmall() $secret = $_SERVER['S3_SECRET'] ?? ''; $bucket = 'utopia-storage-test'; - $device = new S3('/root', $key, $secret, $bucket, S3::EU_CENTRAL_1, S3::ACL_PRIVATE); + $device = new AWS('/root', $key, $secret, $bucket, AWS::EU_CENTRAL_1, AWS::ACL_PRIVATE); $path = $this->object->getPath('text-for-read.txt'); $this->object->write($path, 'Hello World'); diff --git a/tests/Storage/S3Base.php b/tests/Storage/S3Base.php index 2218ce0c..f7535a53 100644 --- a/tests/Storage/S3Base.php +++ b/tests/Storage/S3Base.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Utopia\Storage\Device\Local; use Utopia\Storage\Device\S3; +use Utopia\Storage\Exception\NotFoundException; abstract class S3Base extends TestCase { @@ -20,11 +21,6 @@ abstract protected function getAdapterName(): string; */ abstract protected function getAdapterDescription(): string; - /** - * @return string - */ - abstract protected function getAdapterType(): string; - /** * @var S3 */ @@ -138,6 +134,12 @@ public function testRead() $this->object->delete($this->object->getPath('text-for-read.txt')); } + public function testReadNonExistentFile() + { + $this->expectException(NotFoundException::class); + $this->object->read($this->object->getPath('non-existent-file.txt')); + } + public function testFileExists() { $this->assertEquals(true, $this->object->exists($this->object->getPath('testing/kitten-1.jpg'))); @@ -415,4 +417,15 @@ public function testTransferSmall() $this->object->delete($path); $device->delete($destination); } + + public function testTransferNonExistentFile() + { + $device = new Local(__DIR__.'/../resources/disk-a'); + + $path = $this->object->getPath('non-existent-file.txt'); + $destination = $device->getPath('hello.txt'); + + $this->expectException(NotFoundException::class); + $this->object->transfer($path, $destination, $device); + } } From 2608dc4cb6295799842feac473a37d76b113d935 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 10 Feb 2026 15:11:37 +0530 Subject: [PATCH 2/3] chore: remove compression support --- composer.json | 5 - composer.lock | 186 +++++++++--------- phpunit.xml | 3 +- src/Storage/Compression/Algorithms/Brotli.php | 104 ---------- src/Storage/Compression/Algorithms/GZIP.php | 43 ---- src/Storage/Compression/Algorithms/LZ4.php | 79 -------- src/Storage/Compression/Algorithms/Snappy.php | 38 ---- src/Storage/Compression/Algorithms/XZ.php | 38 ---- src/Storage/Compression/Algorithms/Zstd.php | 79 -------- src/Storage/Compression/Compression.php | 39 ---- .../Compression/Algorithms/BrotliTest.php | 108 ---------- .../Compression/Algorithms/GZIPTest.php | 99 ---------- .../Compression/Algorithms/LZ4Test.php | 77 -------- .../Compression/Algorithms/SnappyTest.php | 81 -------- .../Storage/Compression/Algorithms/XZTest.php | 77 -------- .../Compression/Algorithms/ZstdTest.php | 96 --------- 16 files changed, 91 insertions(+), 1061 deletions(-) delete mode 100644 src/Storage/Compression/Algorithms/Brotli.php delete mode 100644 src/Storage/Compression/Algorithms/GZIP.php delete mode 100644 src/Storage/Compression/Algorithms/LZ4.php delete mode 100644 src/Storage/Compression/Algorithms/Snappy.php delete mode 100644 src/Storage/Compression/Algorithms/XZ.php delete mode 100644 src/Storage/Compression/Algorithms/Zstd.php delete mode 100644 src/Storage/Compression/Compression.php delete mode 100644 tests/Storage/Compression/Algorithms/BrotliTest.php delete mode 100644 tests/Storage/Compression/Algorithms/GZIPTest.php delete mode 100644 tests/Storage/Compression/Algorithms/LZ4Test.php delete mode 100644 tests/Storage/Compression/Algorithms/SnappyTest.php delete mode 100644 tests/Storage/Compression/Algorithms/XZTest.php delete mode 100644 tests/Storage/Compression/Algorithms/ZstdTest.php diff --git a/composer.json b/composer.json index c3b8f2d1..a36f0a7b 100644 --- a/composer.json +++ b/composer.json @@ -18,13 +18,8 @@ "require": { "ext-fileinfo": "*", "ext-zlib": "*", - "ext-zstd": "*", - "ext-xz": "*", - "ext-brotli": "*", "ext-curl": "*", - "ext-lz4": "*", "ext-simplexml": "*", - "ext-snappy": "*", "php": ">=8.1", "utopia-php/system": "0.*.*", "utopia-php/telemetry": "0.2.*", diff --git a/composer.lock b/composer.lock index 25c24113..6356d0ab 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c461b7a9538f9b1c1374f981efe3f81b", + "content-hash": "2be3136f9b8800836ecfc49ca9ab3f42", "packages": [ { "name": "brick/math", - "version": "0.14.1", + "version": "0.14.7", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "url": "https://api.github.com/repos/brick/math/zipball/07ff363b16ef8aca9692bba3be9e73fe63f34e50", + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50", "shasum": "" }, "require": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.1" + "source": "https://github.com/brick/math/tree/0.14.7" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2025-11-24T14:40:29+00:00" + "time": "2026-02-07T10:57:35+00:00" }, { "name": "composer/semver", @@ -145,16 +145,16 @@ }, { "name": "google/protobuf", - "version": "v4.33.2", + "version": "v4.33.5", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318" + "reference": "ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", - "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d", + "reference": "ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d", "shasum": "" }, "require": { @@ -183,9 +183,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.2" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.5" }, - "time": "2025-12-05T22:12:22+00:00" + "time": "2026-01-29T20:49:00+00:00" }, { "name": "nyholm/psr7", @@ -333,16 +333,16 @@ }, { "name": "open-telemetry/api", - "version": "1.7.1", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4" + "reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", - "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/df5197c6fd0ddd8e9883b87de042d9341300e2ad", + "reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad", "shasum": "" }, "require": { @@ -352,7 +352,7 @@ "symfony/polyfill-php82": "^1.26" }, "conflict": { - "open-telemetry/sdk": "<=1.0.8" + "open-telemetry/sdk": "<=1.11" }, "type": "library", "extra": { @@ -399,7 +399,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-10-19T10:49:48+00:00" + "time": "2026-01-21T04:14:03+00:00" }, { "name": "open-telemetry/context", @@ -462,16 +462,16 @@ }, { "name": "open-telemetry/exporter-otlp", - "version": "1.3.3", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d" + "reference": "62e680d587beb42e5247aa6ecd89ad1ca406e8ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/07b02bc71838463f6edcc78d3485c04b48fb263d", - "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/62e680d587beb42e5247aa6ecd89ad1ca406e8ca", + "reference": "62e680d587beb42e5247aa6ecd89ad1ca406e8ca", "shasum": "" }, "require": { @@ -522,7 +522,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-11-13T08:04:37+00:00" + "time": "2026-01-15T09:31:34+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", @@ -589,16 +589,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.10.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99" + "reference": "7f1bd524465c1ca42755a9ef1143ba09913f5be0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", - "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/7f1bd524465c1ca42755a9ef1143ba09913f5be0", + "reference": "7f1bd524465c1ca42755a9ef1143ba09913f5be0", "shasum": "" }, "require": { @@ -639,7 +639,7 @@ ] }, "branch-alias": { - "dev-main": "1.9.x-dev" + "dev-main": "1.12.x-dev" } }, "autoload": { @@ -682,20 +682,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-11-25T10:59:15+00:00" + "time": "2026-01-21T04:14:03+00:00" }, { "name": "open-telemetry/sem-conv", - "version": "1.37.0", + "version": "1.38.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1" + "reference": "e613bc640a407def4991b8a936a9b27edd9a3240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1", - "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/e613bc640a407def4991b8a936a9b27edd9a3240", + "reference": "e613bc640a407def4991b8a936a9b27edd9a3240", "shasum": "" }, "require": { @@ -735,11 +735,11 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-03T12:08:10+00:00" + "time": "2026-01-21T04:14:03+00:00" }, { "name": "php-http/discovery", @@ -1306,16 +1306,16 @@ }, { "name": "symfony/http-client", - "version": "v7.4.3", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616" + "reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616", - "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616", + "url": "https://api.github.com/repos/symfony/http-client/zipball/84bb634857a893cc146cceb467e31b3f02c5fe9f", + "reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f", "shasum": "" }, "require": { @@ -1383,7 +1383,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.3" + "source": "https://github.com/symfony/http-client/tree/v7.4.5" }, "funding": [ { @@ -1403,7 +1403,7 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/http-client-contracts", @@ -1869,16 +1869,16 @@ }, { "name": "utopia-php/system", - "version": "0.9.0", + "version": "0.10.0", "source": { "type": "git", "url": "https://github.com/utopia-php/system.git", - "reference": "8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d" + "reference": "6441a9c180958a373e5ddb330264dd638539dfdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/system/zipball/8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d", - "reference": "8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d", + "url": "https://api.github.com/repos/utopia-php/system/zipball/6441a9c180958a373e5ddb330264dd638539dfdb", + "reference": "6441a9c180958a373e5ddb330264dd638539dfdb", "shasum": "" }, "require": { @@ -1886,7 +1886,7 @@ }, "require-dev": { "laravel/pint": "1.13.*", - "phpstan/phpstan": "1.10.*", + "phpstan/phpstan": "1.12.*", "phpunit/phpunit": "9.6.*" }, "type": "library", @@ -1919,9 +1919,9 @@ ], "support": { "issues": "https://github.com/utopia-php/system/issues", - "source": "https://github.com/utopia-php/system/tree/0.9.0" + "source": "https://github.com/utopia-php/system/tree/0.10.0" }, - "time": "2024-10-09T14:44:01+00:00" + "time": "2025-10-15T19:12:00+00:00" }, { "name": "utopia-php/telemetry", @@ -2356,29 +2356,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.5", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "conflict": { - "phpunit/phpunit": "<=7.5 || >=13" + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^13", - "phpstan/phpstan": "1.4.10 || 2.1.11", + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -2398,36 +2398,35 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "time": "2025-04-07T20:06:18+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.4" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^14", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -2454,7 +2453,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/2.1.0" }, "funding": [ { @@ -2470,7 +2469,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2026-01-05T06:47:08+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -3149,16 +3148,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.0", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", - "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { @@ -3190,9 +3189,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "time": "2025-08-30T15:50:23+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3515,16 +3514,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.31", + "version": "9.6.34", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "945d0b7f346a084ce5549e95289962972c4272e5" + "reference": "b36f02317466907a230d3aa1d34467041271ef4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/945d0b7f346a084ce5549e95289962972c4272e5", - "reference": "945d0b7f346a084ce5549e95289962972c4272e5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a", + "reference": "b36f02317466907a230d3aa1d34467041271ef4a", "shasum": "" }, "require": { @@ -3546,7 +3545,7 @@ "phpunit/php-timer": "^5.0.3", "sebastian/cli-parser": "^1.0.2", "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.9", + "sebastian/comparator": "^4.0.10", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", "sebastian/exporter": "^4.0.8", @@ -3598,7 +3597,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.31" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34" }, "funding": [ { @@ -3622,7 +3621,7 @@ "type": "tidelift" } ], - "time": "2025-12-06T07:45:52+00:00" + "time": "2026-01-27T05:45:00+00:00" }, { "name": "sebastian/cli-parser", @@ -3793,16 +3792,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.9", + "version": "4.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", - "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", "shasum": "" }, "require": { @@ -3855,7 +3854,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" }, "funding": [ { @@ -3875,7 +3874,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T06:51:50+00:00" + "time": "2026-01-24T09:22:56+00:00" }, { "name": "sebastian/complexity", @@ -5559,15 +5558,10 @@ "platform": { "ext-fileinfo": "*", "ext-zlib": "*", - "ext-zstd": "*", - "ext-xz": "*", - "ext-brotli": "*", "ext-curl": "*", - "ext-lz4": "*", "ext-simplexml": "*", - "ext-snappy": "*", "php": ">=8.1" }, "platform-dev": {}, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } diff --git a/phpunit.xml b/phpunit.xml index 1eb9dd9d..f6835d2a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,7 +9,6 @@ stopOnFailure="false"> - ./tests/Storage/Compression ./tests/Storage/Device/LocalTest.php ./tests/Storage/Validator ./tests/Storage/StorageTest.php @@ -18,4 +17,4 @@ ./tests/Storage/Device - \ No newline at end of file + diff --git a/src/Storage/Compression/Algorithms/Brotli.php b/src/Storage/Compression/Algorithms/Brotli.php deleted file mode 100644 index e82c49cc..00000000 --- a/src/Storage/Compression/Algorithms/Brotli.php +++ /dev/null @@ -1,104 +0,0 @@ -level; - } - - /** - * Sets the brotli compression mode to generic. - * - * This is the default mode - */ - public function useGenericMode(): void - { - $this->mode = BROTLI_GENERIC; - } - - /** - * Sets the brotli compression mode to UTF-8 text mode. - * - * Optimizes compression for UTF-8 formatted text - * - * @link https://github.com/kjdev/php-ext-brotli#parameters - */ - public function useTextMode(): void - { - $this->mode = BROTLI_TEXT; - } - - /** - * Sets the brotli compression mode to font mode. - * - * Optimized compression for WOFF 2.0 Fonts - * - * @link https://github.com/kjdev/php-ext-brotli#parameters - */ - public function useFontMode(): void - { - $this->mode = BROTLI_FONT; - } - - /** - * Set the compression level. - * - * Allow values from 0 up to a current max of 11. - * - * @param int $level - * @return void - */ - public function setLevel(int $level): void - { - $min = BROTLI_COMPRESS_LEVEL_MIN; - $max = BROTLI_COMPRESS_LEVEL_MAX; - if ($level < $min || $level > $max) { - throw new \InvalidArgumentException("Level must be between {$min} and {$max}"); - } - $this->level = $level; // $level; - } - - /** - * Compress. - * - * @param string $data - * @return string - */ - public function compress(string $data): string - { - return \brotli_compress($data, $this->getLevel(), $this->mode); - } - - /** - * Decompress. - * - * @param string $data - * @return string - */ - public function decompress(string $data): string - { - return \brotli_uncompress($data); - } -} diff --git a/src/Storage/Compression/Algorithms/GZIP.php b/src/Storage/Compression/Algorithms/GZIP.php deleted file mode 100644 index e047a266..00000000 --- a/src/Storage/Compression/Algorithms/GZIP.php +++ /dev/null @@ -1,43 +0,0 @@ -level = $level; - } - - /** - * Get the compression level. - * - * @return int - */ - public function getLevel(): int - { - return $this->level; - } - - /** - * Set the compression level. - * - * Allow values from 0 up to a current max of 12. - * - * @param int $level - * @return void - */ - public function setLevel(int $level): void - { - if ($level < 0 || $level > 12) { - throw new \InvalidArgumentException('Level must be between 0 and 12'); - } - $this->level = $level; - } - - /** - * Get the name of the algorithm. - * - * @return string - */ - public function getName(): string - { - return 'lz4'; - } - - /** - * Compress. - * - * @param string $data - * @return string - */ - public function compress(string $data): string - { - return \lz4_compress($data, $this->level); - } - - /** - * Decompress. - * - * @param string $data - * @return string - */ - public function decompress(string $data): string - { - return \lz4_uncompress($data); - } -} diff --git a/src/Storage/Compression/Algorithms/Snappy.php b/src/Storage/Compression/Algorithms/Snappy.php deleted file mode 100644 index 32af571f..00000000 --- a/src/Storage/Compression/Algorithms/Snappy.php +++ /dev/null @@ -1,38 +0,0 @@ -= 20 should be used with caution, as they require more memory. - * - * Default value is 3. - */ - protected int $level = 3; - - public function __construct(int $level = 3) - { - $this->level = $level; - } - - /** - * Get the compression level. - * - * @return int - */ - public function getLevel(): int - { - return $this->level; - } - - /** - * Set the compression level. - * - * Allow values from 1 up to a current max of 22. - * - * @param int $level - * @return void - */ - public function setLevel(int $level): void - { - if ($level < 1 || $level > 22) { - throw new \InvalidArgumentException('Level must be between 1 and 22'); - } - $this->level = $level; - } - - /** - * Get the name of the algorithm. - * - * @return string - */ - public function getName(): string - { - return 'zstd'; - } - - /** - * Compress. - * - * @param string $data - * @return string - */ - public function compress(string $data): string - { - return \zstd_compress($data, $this->level); - } - - /** - * Decompress. - * - * @param string $data - * @return string - */ - public function decompress(string $data): string - { - return \zstd_uncompress($data); - } -} diff --git a/src/Storage/Compression/Compression.php b/src/Storage/Compression/Compression.php deleted file mode 100644 index 57b57d90..00000000 --- a/src/Storage/Compression/Compression.php +++ /dev/null @@ -1,39 +0,0 @@ -object = new Brotli(); - } - - public function tearDown(): void - { - } - - public function testName() - { - $this->assertEquals($this->object->getName(), 'brotli'); - } - - public function testErrorsWhenSettingLevel() - { - $this->expectException(InvalidArgumentException::class); - $this->object->setLevel(-1); - } - - public function testCompressDecompressWithText() - { - $demo = 'This is a demo string'; - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 21); - $this->assertEquals($dataSize, 25); - - $this->assertEquals($this->object->decompress($data), $demo); - } - - public function testCompressDecompressWithLargeText() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/lorem.txt'); - $demoSize = mb_strlen($demo, '8bit'); - - $this->object->setLevel(8); - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 386795); - $this->assertEquals($dataSize, 33128); - - $this->assertGreaterThan($dataSize, $demoSize); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 386795); - $this->assertEquals($data, $demo); - } - - public function testCompressDecompressWithJPGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); - $demoSize = mb_strlen($demo, '8bit'); - - $this->object->setLevel(8); - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 599639); - // brotli is not the best for images - $this->assertEquals($dataSize, 599644); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 599639); - } - - public function testCompressDecompressWithPNGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); - $demoSize = mb_strlen($demo, '8bit'); - - $this->object->setLevel(8); - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 3038056); - // brotli is not the best for images - $this->assertEquals($dataSize, 3038068); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 3038056); - } -} diff --git a/tests/Storage/Compression/Algorithms/GZIPTest.php b/tests/Storage/Compression/Algorithms/GZIPTest.php deleted file mode 100644 index 1f5a8446..00000000 --- a/tests/Storage/Compression/Algorithms/GZIPTest.php +++ /dev/null @@ -1,99 +0,0 @@ -object = new GZIP(); - } - - public function tearDown(): void - { - } - - public function testName() - { - $this->assertEquals($this->object->getName(), 'gzip'); - } - - public function testCompressDecompressWithText() - { - $demo = 'This is a demo string'; - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 21); - $this->assertEquals($dataSize, 39); - - $this->assertEquals($this->object->decompress($data), $demo); - } - - public function testCompressDecompressWithLargeText() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/lorem.txt'); - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 386795); - $this->assertEquals($dataSize, 44444); - - $this->assertGreaterThan($dataSize, $demoSize); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 386795); - } - - public function testCompressDecompressWithJPGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 599639); - $this->assertEquals($dataSize, 599107); - - $this->assertGreaterThan($dataSize, $demoSize); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 599639); - } - - public function testCompressDecompressWithPNGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 3038056); - $this->assertEquals($dataSize, 3029202); - - $this->assertGreaterThan($dataSize, $demoSize); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 3038056); - } -} diff --git a/tests/Storage/Compression/Algorithms/LZ4Test.php b/tests/Storage/Compression/Algorithms/LZ4Test.php deleted file mode 100644 index 9d66ee85..00000000 --- a/tests/Storage/Compression/Algorithms/LZ4Test.php +++ /dev/null @@ -1,77 +0,0 @@ -object = new LZ4(); - } - - public function tearDown(): void - { - } - - public function testName() - { - $this->assertEquals($this->object->getName(), 'lz4'); - } - - public function testCompressDecompressWithText() - { - $demo = 'This is a demo string'; - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(21, $demoSize); - $this->assertEquals(27, $dataSize); - - $this->assertEquals($demo, $this->object->decompress($data)); - } - - public function testCompressDecompressWithJPGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(599639, $demoSize); - $this->assertEquals(601828, $dataSize); - - $this->assertGreaterThan($demoSize, $dataSize); - - $data = $this->object->decompress($data); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(599639, $dataSize); - } - - public function testCompressDecompressWithPNGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(3038056, $demoSize); - $this->assertEquals(3049975, $dataSize); - - $this->assertGreaterThan($demoSize, $dataSize); - - $data = $this->object->decompress($data); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(3038056, $dataSize); - } -} diff --git a/tests/Storage/Compression/Algorithms/SnappyTest.php b/tests/Storage/Compression/Algorithms/SnappyTest.php deleted file mode 100644 index 3a752e92..00000000 --- a/tests/Storage/Compression/Algorithms/SnappyTest.php +++ /dev/null @@ -1,81 +0,0 @@ -object = new Snappy(); - } - - public function tearDown(): void - { - } - - public function testName() - { - $this->assertEquals($this->object->getName(), 'snappy'); - } - - public function testCompressDecompressWithText() - { - $demo = 'This is a demo string'; - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(21, $demoSize); - $this->assertEquals(23, $dataSize); - - $this->assertEquals($this->object->decompress($data), $demo); - } - - public function testCompressDecompressWithJPGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(599639, $demoSize); - $this->assertEquals(599504, $dataSize); - - $this->assertGreaterThan($dataSize, $demoSize); - - $data = $this->object->decompress($data); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(599639, $dataSize); - } - - public function testCompressDecompressWithPNGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(3038056, $demoSize); - $this->assertEquals(3038200, $dataSize); - - $this->assertGreaterThan($demoSize, $dataSize); - - $data = $this->object->decompress($data); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(3038056, $dataSize); - } -} diff --git a/tests/Storage/Compression/Algorithms/XZTest.php b/tests/Storage/Compression/Algorithms/XZTest.php deleted file mode 100644 index 2b55ee01..00000000 --- a/tests/Storage/Compression/Algorithms/XZTest.php +++ /dev/null @@ -1,77 +0,0 @@ -object = new XZ(); - } - - public function tearDown(): void - { - } - - public function testName() - { - $this->assertEquals($this->object->getName(), 'xz'); - } - - public function testCompressDecompressWithText() - { - $demo = 'This is a demo string'; - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 21); - $this->assertEquals($dataSize, 80); - - $this->assertEquals($this->object->decompress($data), $demo); - } - - public function testCompressDecompressWithJPGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 599639); - $this->assertEquals($dataSize, 599432); - - $this->assertGreaterThan($dataSize, $demoSize); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 599639); - } - - public function testCompressDecompressWithPNGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 3038056); - $this->assertEquals($dataSize, 2981000); - - $this->assertGreaterThan($dataSize, $demoSize); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 3038056); - } -} diff --git a/tests/Storage/Compression/Algorithms/ZstdTest.php b/tests/Storage/Compression/Algorithms/ZstdTest.php deleted file mode 100644 index 488cd290..00000000 --- a/tests/Storage/Compression/Algorithms/ZstdTest.php +++ /dev/null @@ -1,96 +0,0 @@ -object = new Zstd(); - } - - public function tearDown(): void - { - } - - public function testName() - { - $this->assertEquals($this->object->getName(), 'zstd'); - } - - public function testCompressDecompressWithText() - { - $demo = 'This is a demo string'; - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(21, $demoSize); - $this->assertEquals(30, $dataSize); - - $this->assertEquals($demo, $this->object->decompress($data)); - } - - public function testCompressDecompressWithLargeText() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/lorem.txt'); - $demoSize = mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($demoSize, 386795); - $this->assertEquals($dataSize, 56324); - - $this->assertGreaterThan($dataSize, $demoSize); - - $data = $this->object->decompress($data); - $dataSize = mb_strlen($data, '8bit'); - - $this->assertEquals($dataSize, 386795); - } - - public function testCompressDecompressWithJPGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-a/kitten-1.jpg'); - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(599639, $demoSize); - $this->assertEquals(599663, $dataSize); - - $this->assertGreaterThan($demoSize, $dataSize); - - $data = $this->object->decompress($data); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(599639, $dataSize); - } - - public function testCompressDecompressWithPNGImage() - { - $demo = \file_get_contents(__DIR__.'/../../../resources/disk-b/kitten-1.png'); - $demoSize = \mb_strlen($demo, '8bit'); - - $data = $this->object->compress($demo); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(3038056, $demoSize); - $this->assertEquals(3038138, $dataSize); - - $this->assertGreaterThan($demoSize, $dataSize); - - $data = $this->object->decompress($data); - $dataSize = \mb_strlen($data, '8bit'); - - $this->assertEquals(3038056, $dataSize); - } -} From e8836794d6196a575e28b5669bc5d7f1138dab6b Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 10 Feb 2026 15:46:59 +0530 Subject: [PATCH 3/3] readme --- README.md | 244 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 225 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 80d9ef6b..f2e5cdd5 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ # Utopia Storage -[![Build Status](https://travis-ci.org/utopia-php/ab.svg?branch=master)](https://travis-ci.com/utopia-php/storage) +[![Build Status](https://travis-ci.org/utopia-php/storage.svg?branch=master)](https://travis-ci.com/utopia-php/storage) ![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/storage.svg) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord)](https://appwrite.io/discord) -Utopia Storage library is simple and lite library for managing application storage. It supports multiple storage adapters. We already support AWS S3 storage, Digitalocean Spaces storage, Backblaze B2 Cloud storage, Linode Object storage and Wasabi Cloud storage. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io). +Utopia Storage is a simple and lightweight library for managing application storage across multiple adapters. This library is designed to be easy to learn and use, with a consistent API regardless of the storage provider. This library is maintained by the [Appwrite team](https://appwrite.io). This library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project. - ## Getting Started Install using composer: @@ -16,39 +15,246 @@ Install using composer: composer require utopia-php/storage ``` +### Basic Usage + ```php upload('/local/path/to/file.png', 'destination/path/file.png'); + +// Check if file exists +$exists = $device->exists('destination/path/file.png'); + +// Read file contents +$contents = $device->read('destination/path/file.png'); + +// Delete a file +$device->delete('destination/path/file.png'); +``` + +## Available Adapters + +### Local Storage + +Use the local filesystem for storing files. + +```php +use Utopia\Storage\Storage; +use Utopia\Storage\Device\Local; + +// Initialize local storage +Storage::setDevice('files', new Local('/path/to/storage')); +``` + +### AWS S3 + +Store files in Amazon S3 or compatible services. + +```php +use Utopia\Storage\Storage; +use Utopia\Storage\Device\S3; + +// Initialize S3 storage +Storage::setDevice('files', new S3( + 'root', // Root path in bucket + 'YOUR_ACCESS_KEY', + 'YOUR_SECRET_KEY', + 'YOUR_BUCKET_NAME', + S3::US_EAST_1, // Region (default: us-east-1) + S3::ACL_PRIVATE // Access control (default: private) +)); + +// Available regions +// S3::US_EAST_1, S3::US_EAST_2, S3::US_WEST_1, S3::US_WEST_2, S3::AP_SOUTH_1, +// S3::AP_NORTHEAST_1, S3::AP_NORTHEAST_2, S3::AP_NORTHEAST_3, S3::AP_SOUTHEAST_1, +// S3::AP_SOUTHEAST_2, S3::EU_CENTRAL_1, S3::EU_WEST_1, S3::EU_WEST_2, S3::EU_WEST_3, +// And more - check the S3 class for all available regions + +// Available ACL options +// S3::ACL_PRIVATE, S3::ACL_PUBLIC_READ, S3::ACL_PUBLIC_READ_WRITE, S3::ACL_AUTHENTICATED_READ +``` + +### DigitalOcean Spaces + +Store files in DigitalOcean Spaces. + +```php +use Utopia\Storage\Storage; +use Utopia\Storage\Device\DOSpaces; + +// Initialize DO Spaces storage +Storage::setDevice('files', new DOSpaces( + 'root', // Root path in bucket + 'YOUR_ACCESS_KEY', + 'YOUR_SECRET_KEY', + 'YOUR_BUCKET_NAME', + DOSpaces::NYC3, // Region (default: nyc3) + DOSpaces::ACL_PRIVATE // Access control (default: private) +)); + +// Available regions +// DOSpaces::NYC3, DOSpaces::SGP1, DOSpaces::FRA1, DOSpaces::SFO2, DOSpaces::SFO3, DOSpaces::AMS3 +``` + +### Backblaze B2 + +Store files in Backblaze B2 Cloud Storage. + +```php +use Utopia\Storage\Storage; +use Utopia\Storage\Device\Backblaze; + +// Initialize Backblaze storage +Storage::setDevice('files', new Backblaze( + 'root', // Root path in bucket + 'YOUR_ACCESS_KEY', + 'YOUR_SECRET_KEY', + 'YOUR_BUCKET_NAME', + Backblaze::US_WEST_004, // Region (default: us-west-004) + Backblaze::ACL_PRIVATE // Access control (default: private) +)); + +// Available regions (clusters) +// Backblaze::US_WEST_000, Backblaze::US_WEST_001, Backblaze::US_WEST_002, +// Backblaze::US_WEST_004, Backblaze::EU_CENTRAL_003 +``` + +### Linode Object Storage + +Store files in Linode Object Storage. + +```php +use Utopia\Storage\Storage; +use Utopia\Storage\Device\Linode; + +// Initialize Linode storage +Storage::setDevice('files', new Linode( + 'root', // Root path in bucket + 'YOUR_ACCESS_KEY', + 'YOUR_SECRET_KEY', + 'YOUR_BUCKET_NAME', + Linode::EU_CENTRAL_1, // Region (default: eu-central-1) + Linode::ACL_PRIVATE // Access control (default: private) +)); + +// Available regions +// Linode::EU_CENTRAL_1, Linode::US_SOUTHEAST_1, Linode::US_EAST_1, Linode::AP_SOUTH_1 +``` + +### Wasabi Cloud Storage + +Store files in Wasabi Cloud Storage. + +```php +use Utopia\Storage\Storage; +use Utopia\Storage\Device\Wasabi; + +// Initialize Wasabi storage +Storage::setDevice('files', new Wasabi( + 'root', // Root path in bucket + 'YOUR_ACCESS_KEY', + 'YOUR_SECRET_KEY', + 'YOUR_BUCKET_NAME', + Wasabi::EU_CENTRAL_1, // Region (default: eu-central-1) + Wasabi::ACL_PRIVATE // Access control (default: private) +)); -// Instantiating local storage -Storage::setDevice('files', new Local('path')); +// Available regions +// Wasabi::US_EAST_1, Wasabi::US_EAST_2, Wasabi::US_WEST_1, Wasabi::US_CENTRAL_1, +// Wasabi::EU_CENTRAL_1, Wasabi::EU_CENTRAL_2, Wasabi::EU_WEST_1, Wasabi::EU_WEST_2, +// Wasabi::AP_NORTHEAST_1, Wasabi::AP_NORTHEAST_2 +``` -// Or you can use AWS S3 storage -Storage::setDevice('files', new S3('path', AWS_ACCESS_KEY, AWS_SECRET_KEY,AWS_BUCKET_NAME, AWS_REGION, AWS_ACL_FLAG)); +## Common Operations -// Or you can use DigitalOcean Spaces storage -Storage::setDevice('files', new DOSpaces('path', DO_SPACES_ACCESS_KEY, DO_SPACES_SECRET_KEY, DO_SPACES_BUCKET_NAME, DO_SPACES_REGION, AWS_ACL_FLAG)); +All storage adapters provide a consistent API for working with files: +```php +// Get storage device $device = Storage::getDevice('files'); -//upload -$device->upload('file.png','path'); +// Upload a file +$device->upload('/path/to/local/file.jpg', 'remote/path/file.jpg'); + +// Check if file exists +$exists = $device->exists('remote/path/file.jpg'); -//delete -$device->delete('path'); +// Get file size +$size = $device->getFileSize('remote/path/file.jpg'); +// Get file MIME type +$mime = $device->getFileMimeType('remote/path/file.jpg'); + +// Get file MD5 hash +$hash = $device->getFileHash('remote/path/file.jpg'); + +// Read file contents +$contents = $device->read('remote/path/file.jpg'); + +// Read partial file contents +$chunk = $device->read('remote/path/file.jpg', 0, 1024); // Read first 1KB + +// Multipart/chunked uploads +$device->upload('/local/file.mp4', 'remote/video.mp4', 1, 3); // Part 1 of 3 + +// Create directory +$device->createDirectory('remote/new-directory'); + +// List files in directory +$files = $device->listFiles('remote/directory'); + +// Delete file +$device->delete('remote/path/file.jpg'); + +// Delete directory +$device->deleteDirectory('remote/directory'); + +// Transfer files between storage devices +$sourceDevice = Storage::getDevice('source'); +$targetDevice = Storage::getDevice('target'); + +$sourceDevice->transfer('source/path.jpg', 'target/path.jpg', $targetDevice); ``` +## Adding New Adapters + +For information on adding new storage adapters, see the [Adding New Storage Adapter](https://github.com/utopia-php/storage/blob/master/docs/adding-new-storage-adapter.md) guide. + ## System Requirements -Utopia Framework requires PHP 7.4 or later. We recommend using the latest PHP version whenever possible. +Utopia Storage requires PHP 7.4 or later. We recommend using the latest PHP version whenever possible. + +## Contributing + +For security issues, please email [security@appwrite.io](mailto:security@appwrite.io) instead of posting a public issue in GitHub. + +All code contributions - including those of people having commit access - must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code. -## Copyright and license +We welcome you to contribute to the Utopia Storage library. For details on how to do this, please refer to our [Contributing Guide](https://github.com/utopia-php/storage/blob/master/CONTRIBUTING.md). -The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) +## License + +This library is available under the MIT License. + +## Copyright + +``` +Copyright (c) 2019-2025 Appwrite Team +```