mubbi / laravel-flysystem-huawei-obs
Laravel Flysystem v3 adapter for Huawei Object Storage Service (OBS) with v2 compatibility
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^6.3.0|^7.0|^8.0
- laravel/framework: ^9.0|^10.48.29|^11.0|^12.0
- league/flysystem: ^2.0|^3.0
- mubbi/esdk-obs-php: ^3.24
Requires (Dev)
- fakerphp/faker: ^1.23
- laravel/pint: ^1.0
- mockery/mockery: ^1.6
- orchestra/testbench: ^7.0|^8.0|^9.0|^10.0
- phpstan/phpstan: ^1.10
- phpstan/phpstan-mockery: ^1.1
- phpstan/phpstan-phpunit: ^1.4
- phpunit/phpunit: ^9.0|^10.0
Provides
- league/flysystem-adapter: 1.2.11
Conflicts
- guzzlehttp/guzzle: <6.3.0|>=9.0
- league/flysystem: <2.0|>=4.0
README
A Laravel Flysystem v2/v3 adapter for Huawei Object Storage Service (OBS). This package provides seamless integration between Laravel's filesystem abstraction and Huawei Cloud OBS, allowing you to use Huawei OBS as a storage backend in your Laravel applications.
Multi-Version Compatibility
This package now supports multiple versions of both Flysystem and Guzzle:
- Flysystem: v3.0+ (primary) with v2.0+ compatibility through dependency constraints
- Guzzle: v6.3.0+, v7.0+, and v8.0+ (automatic detection)
- Laravel: 9.0+, 10.0+, 11.0+, and 12.0+
- PHP: 8.1+
Features
- ✅ Complete Flysystem v3 Compatibility: Full implementation of all required and optional Flysystem methods
- ✅ Laravel Integration: Seamless integration with Laravel's Storage facade
- ✅ Huawei OBS SDK Integration: Uses
mubbi/esdk-obs-php
(Huawei OBS SDK compatible fork) - ✅ Temporary Credentials: Support for session tokens (
securityToken
) - ✅ Signed URLs: Generate pre-signed URLs for temporary object access
- ✅ Public URLs: Generate public URLs for objects with public read access
- ✅ Post Signatures: Create signatures for direct browser uploads to OBS
- ✅ Object Tagging: Add and manage metadata tags on OBS objects
- ✅ Object Restoration: Restore archived objects from OBS
- ✅ Credential Refresh: Update credentials during runtime
- ✅ Authentication Validation: Proactive checking of OBS credentials and bucket access
- ✅ Comprehensive Error Handling: Clear, actionable error messages
- ✅ Security-Focused Design: Private visibility by default, SSL verification
- ✅ Configuration Validation: Automatic validation of required configuration parameters
- ✅ Retry Logic: Automatic retry with exponential backoff for transient errors
- ✅ Authentication Caching: Cache authentication status to improve performance
- ✅ Logging Support: Optional operation and error logging
- ✅ Artisan Commands: Built-in testing command for connectivity verification
- ✅ Type Safety: PHPStan level 8 compliance with strict typing
- ✅ Code Quality: Laravel Pint compliance and PSR-12 standards
- ✅ Performance Optimized: Built-in timeout protection and infinite loop prevention
- ✅ Large Dataset Support: Optimized methods for handling large numbers of files
Security Features
This package includes robust security features:
- 🔐 Authentication Validation: Automatic authentication checks before all operations
- 🛡️ Secure Defaults: Private visibility by default, SSL verification enabled
- 🔑 Temporary Credentials: Support for security tokens and credential rotation
- 🌐 Signed URLs: Secure temporary access without exposing credentials
- 📝 Input Sanitization: Path normalization and validation
- 🚫 Error Handling: No sensitive data exposure in error messages
- 🔄 Infinite Loop Prevention: Built-in safety mechanisms to prevent timeouts
Requirements
- PHP 8.1+
- Laravel 9.0+ (supports Laravel 9, 10.48.29+, 11, and 12)
- Flysystem v3.0+ (primary) with v2.0+ compatibility
- Guzzle v6.3.0+, v7.0+, or v8.0+ (automatic detection)
- Huawei Cloud OBS account and credentials
⚠️ Security Notice: Laravel 10.0.0 to 10.48.28 contains a file validation bypass vulnerability (CVE-2025-27515). This package requires Laravel 10.48.29+ to ensure security. Please upgrade your Laravel installation if you're using an affected version.
Installation
Version Compatibility
This package automatically detects and adapts to your installed versions of Guzzle:
- Guzzle v6: Uses v6-specific client configurations
- Guzzle v7: Uses v7-specific client configurations
- Guzzle v8: Uses v8-specific client configurations (with PSR-18 compliance)
For Flysystem, the package uses v3 as the primary interface but maintains compatibility with v2 through dependency constraints. The package will automatically choose the correct HTTP client configuration based on your installed Guzzle version.
Installation Steps
- Install the package via Composer:
composer require mubbi/laravel-flysystem-huawei-obs
- Publish the configuration (optional):
php artisan vendor:publish --provider="LaravelFlysystemHuaweiObs\HuaweiObsServiceProvider" --tag=huawei-obs-config
- Add your Huawei OBS credentials to your
.env
file:
# Required Configuration HUAWEI_OBS_ACCESS_KEY_ID=your_access_key_id HUAWEI_OBS_SECRET_ACCESS_KEY=your_secret_access_key HUAWEI_OBS_BUCKET=your_bucket_name HUAWEI_OBS_ENDPOINT=https://obs.cn-north-1.myhuaweicloud.com # Optional Configuration HUAWEI_OBS_PREFIX=optional_prefix HUAWEI_OBS_SECURITY_TOKEN=your_security_token_for_temporary_credentials # Advanced OBSClient Configuration HUAWEI_OBS_SIGNATURE=v4 HUAWEI_OBS_PATH_STYLE=false HUAWEI_OBS_REGION=cn-north-1 HUAWEI_OBS_SSL_CERTIFICATE_AUTHORITY= HUAWEI_OBS_MAX_RETRY_COUNT=3 HUAWEI_OBS_SOCKET_TIMEOUT=60 HUAWEI_OBS_CHUNK_SIZE=8192 HUAWEI_OBS_EXCEPTION_RESPONSE_MODE=exception HUAWEI_OBS_IS_CNAME=false # HTTP Client Configuration HUAWEI_OBS_TIMEOUT=120 HUAWEI_OBS_CONNECT_TIMEOUT=30 HUAWEI_OBS_VERIFY_SSL=true # Retry Configuration HUAWEI_OBS_RETRY_ATTEMPTS=3 HUAWEI_OBS_RETRY_DELAY=1 # Logging Configuration HUAWEI_OBS_LOGGING_ENABLED=false HUAWEI_OBS_LOG_OPERATIONS=false HUAWEI_OBS_LOG_ERRORS=true
- Configure your filesystem in
config/filesystems.php
:
'disks' => [ 'huawei-obs' => [ 'driver' => 'huawei-obs', // Required Configuration 'key' => env('HUAWEI_OBS_ACCESS_KEY_ID'), 'secret' => env('HUAWEI_OBS_SECRET_ACCESS_KEY'), 'bucket' => env('HUAWEI_OBS_BUCKET'), 'endpoint' => env('HUAWEI_OBS_ENDPOINT'), // Optional Configuration 'prefix' => env('HUAWEI_OBS_PREFIX'), 'security_token' => env('HUAWEI_OBS_SECURITY_TOKEN'), // Advanced OBSClient Configuration 'signature' => env('HUAWEI_OBS_SIGNATURE', 'v4'), 'path_style' => env('HUAWEI_OBS_PATH_STYLE', false), 'region' => env('HUAWEI_OBS_REGION'), 'ssl_verify' => env('HUAWEI_OBS_VERIFY_SSL', true), 'ssl.certificate_authority' => env('HUAWEI_OBS_SSL_CERTIFICATE_AUTHORITY'), 'max_retry_count' => env('HUAWEI_OBS_MAX_RETRY_COUNT', 3), 'timeout' => env('HUAWEI_OBS_TIMEOUT', 120), 'socket_timeout' => env('HUAWEI_OBS_SOCKET_TIMEOUT', 60), 'connect_timeout' => env('HUAWEI_OBS_CONNECT_TIMEOUT', 30), 'chunk_size' => env('HUAWEI_OBS_CHUNK_SIZE', 8192), 'exception_response_mode' => env('HUAWEI_OBS_EXCEPTION_RESPONSE_MODE', 'exception'), 'is_cname' => env('HUAWEI_OBS_IS_CNAME', false), // HTTP Client Configuration 'http_client' => [ 'timeout' => env('HUAWEI_OBS_TIMEOUT', 120), 'connect_timeout' => env('HUAWEI_OBS_CONNECT_TIMEOUT', 30), 'verify' => env('HUAWEI_OBS_VERIFY_SSL', true), 'proxy' => null, 'headers' => [], ], // Retry Configuration 'retry_attempts' => env('HUAWEI_OBS_RETRY_ATTEMPTS', 3), 'retry_delay' => env('HUAWEI_OBS_RETRY_DELAY', 1), // Logging Configuration 'logging_enabled' => env('HUAWEI_OBS_LOGGING_ENABLED', false), 'log_operations' => env('HUAWEI_OBS_LOG_OPERATIONS', false), 'log_errors' => env('HUAWEI_OBS_LOG_ERRORS', true), // Flysystem Configuration 'visibility' => 'private', 'throw' => false, ], ],
Basic Usage
Laravel Storage Facade
The package integrates seamlessly with Laravel's Storage facade:
use Illuminate\Support\Facades\Storage; // Upload a file Storage::disk('huawei-obs')->put('file.txt', 'Hello World'); // Download a file $contents = Storage::disk('huawei-obs')->get('file.txt'); // Check if file exists $exists = Storage::disk('huawei-obs')->exists('file.txt'); // Delete a file Storage::disk('huawei-obs')->delete('file.txt'); // Get file URL $url = Storage::disk('huawei-obs')->url('file.txt'); // Get temporary URL $tempUrl = Storage::disk('huawei-obs')->temporaryUrl('file.txt', now()->addHour());
Direct Adapter Usage
You can also use the adapter directly (Flysystem v3 interface):
use LaravelFlysystemHuaweiObs\LaravelHuaweiObsAdapter; use League\Flysystem\Filesystem; use League\Flysystem\Config; $adapter = new LaravelHuaweiObsAdapter( env('HUAWEI_OBS_ACCESS_KEY_ID'), env('HUAWEI_OBS_SECRET_ACCESS_KEY'), env('HUAWEI_OBS_BUCKET'), env('HUAWEI_OBS_ENDPOINT'), env('HUAWEI_OBS_PREFIX'), ); $filesystem = new Filesystem($adapter); $filesystem->write('file.txt', 'Hello World', new Config()); $contents = $filesystem->read('file.txt');
Configuration Options
Required Configuration
These options are required for the adapter to function:
key
- Your Huawei OBS Access Key IDsecret
- Your Huawei OBS Secret Access Keybucket
- The OBS bucket nameendpoint
- The OBS endpoint URL (e.g.,https://obs.cn-north-1.myhuaweicloud.com
)
Optional Configuration
prefix
- Optional path prefix for all operationssecurity_token
- Security token for temporary credentials
Advanced OBSClient Configuration
The adapter supports all OBSClient configuration options:
signature
- Signature version (v2
,v4
,obs
) - Default:v4
path_style
- Use path-style URLs instead of virtual-hosted-style - Default:false
region
- OBS region (e.g.,cn-north-1
) - Optionalssl_verify
- Enable/disable SSL certificate verification - Default:true
ssl.certificate_authority
- Path to custom CA certificate bundle - Optionalmax_retry_count
- Maximum number of retry attempts - Default:3
timeout
- Request timeout in seconds - Default:120
socket_timeout
- Socket timeout in seconds - Default:60
connect_timeout
- Connection timeout in seconds - Default:30
chunk_size
- Chunk size for multipart uploads in bytes - Default:8192
exception_response_mode
- Exception response mode (exception
,response
) - Default:exception
is_cname
- Whether the endpoint is a CNAME - Default:false
HTTP Client Configuration
http_client.timeout
- Request timeout in seconds - Default:120
http_client.connect_timeout
- Connection timeout in seconds - Default:30
http_client.verify
- SSL certificate verification - Default:true
http_client.proxy
- HTTP proxy configuration - Optionalhttp_client.headers
- Additional HTTP headers - Optional
Retry Configuration
retry_attempts
- Number of retry attempts for transient errors - Default:3
retry_delay
- Base delay between retries in seconds - Default:1
Logging Configuration
logging_enabled
- Enable/disable logging - Default:false
log_operations
- Log successful operations - Default:false
log_errors
- Log errors - Default:true
Examples
See the examples/
directory for concise, copy-pasteable snippets:
examples/usage.php
: basic write/read with Flysystemexamples/url-usage.php
: URL and temporary URL usageexamples/advanced-usage.php
: signed URLs, post signatures, tags, restore, optimized listingexamples/controller-compatibility.php
: controller-style listing exampleexamples/laravel-compatibility-demo.php
: Storage facade compatibility helpers
Advanced Features
Optimized Methods for Large Datasets
For applications with large numbers of files, the package provides optimized methods with built-in timeout protection:
// Get storage statistics with timeout protection $stats = Storage::disk('huawei-obs')->getStorageStats(10000, 60); // Returns: ['total_files' => 1234, 'total_directories' => 56, 'total_size_bytes' => 1073741824, ...] // Get files with limits and timeout $files = Storage::disk('huawei-obs')->allFilesOptimized(1000, 30); // Get directories with limits and timeout $directories = Storage::disk('huawei-obs')->allDirectoriesOptimized(1000, 30); // List contents with advanced controls foreach (Storage::disk('huawei-obs')->listContentsOptimized('path', true, 1000, 60) as $item) { // Process items with timeout protection }
Retry Logic and Error Handling
The adapter includes automatic retry logic with exponential backoff for transient errors:
// Configure retry behavior 'huawei-obs' => [ 'retry_attempts' => 3, // Number of retry attempts 'retry_delay' => 1, // Base delay in seconds // ... other config ],
The retry logic will automatically retry on transient errors but will not retry on:
- Authentication errors (
AccessDenied
,InvalidAccessKeyId
,SignatureDoesNotMatch
) - Configuration errors (
NoSuchBucket
)
Authentication Caching
Authentication status is cached for 5 minutes to improve performance:
// Refresh authentication cache manually $adapter->refreshAuthentication(); // Refresh credentials (clears cache automatically) $adapter->refreshCredentials('new-key', 'new-secret', 'new-token');
Logging
Enable operation and error logging:
'huawei-obs' => [ 'logging_enabled' => true, 'log_operations' => true, // Log successful operations 'log_errors' => true, // Log errors // ... other config ],
Laravel Storage Facade Compatibility
This package provides full compatibility with Laravel's Storage facade. All the standard Laravel Storage methods are supported:
Directory Listing
// List files in a directory (non-recursive) $files = Storage::disk('huawei-obs')->files('uploads'); // List directories in a directory (non-recursive) $directories = Storage::disk('huawei-obs')->directories('uploads'); // List all files and directories (recursive) $allFiles = Storage::disk('huawei-obs')->allFiles(); $allDirectories = Storage::disk('huawei-obs')->allDirectories();
File Information
// Check if file exists $exists = Storage::disk('huawei-obs')->exists('file.txt'); // Get file size $size = Storage::disk('huawei-obs')->size('file.txt'); // Get last modified timestamp $modified = Storage::disk('huawei-obs')->lastModified('file.txt'); // Get MIME type $mimeType = Storage::disk('huawei-obs')->mimeType('file.txt'); // Get file visibility $visibility = Storage::disk('huawei-obs')->visibility('file.txt');
Complete Example
$directory = $request->get('directory', ''); $files = Storage::disk('huawei-obs')->files($directory); $directories = Storage::disk('huawei-obs')->directories($directory); $fileDetails = []; foreach ($files as $file) { $fileDetails[] = [ 'name' => $file, 'size' => Storage::disk('huawei-obs')->size($file), 'last_modified' => Storage::disk('huawei-obs')->lastModified($file), 'mime_type' => Storage::disk('huawei-obs')->mimeType($file), 'url' => Storage::disk('huawei-obs')->url($file), 'visibility' => Storage::disk('huawei-obs')->visibility($file) ]; } return response()->json([ 'success' => true, 'files' => $fileDetails, 'directories' => $directories, 'total_files' => count($files), 'total_directories' => count($directories) ]);
Logged information includes:
- Operation name and duration
- File path and bucket
- Error details and codes
- Additional context (tags, headers, etc.)
Configuration Validation
The service provider automatically validates required configuration:
// Missing required fields will throw clear errors 'huawei-obs' => [ 'key' => '', // ❌ Will throw: "Missing required configuration: key" // ... other config ],
Custom Exceptions
The package provides custom exceptions for better error handling:
use LaravelFlysystemHuaweiObs\Exceptions\UnableToCreateSignedUrl; use LaravelFlysystemHuaweiObs\Exceptions\UnableToSetObjectTags; try { $signedUrl = $adapter->createSignedUrl('file.txt'); } catch (UnableToCreateSignedUrl $e) { // Handle signed URL creation errors } try { $adapter->setObjectTags('file.txt', ['tag' => 'value']); } catch (UnableToSetObjectTags $e) { // Handle object tagging errors }
Authentication & Security
The adapter automatically validates authentication before operations and provides clear error messages:
try { Storage::disk('huawei-obs')->exists('file.txt'); } catch (\RuntimeException $e) { // Clear authentication error message // "Authentication failed. Please check your Huawei OBS credentials..." }
Temporary Credentials (Security Token)
For applications using temporary credentials (like AWS STS or similar services):
// Configure with security token $adapter = new \LaravelFlysystemHuaweiObs\HuaweiObsAdapter( 'access_key_id', 'secret_access_key', 'bucket_name', 'endpoint', null, // prefix null, // http client 'security_token_here' ); // Or refresh credentials during runtime $adapter->refreshCredentials('new_access_key', 'new_secret_key', 'new_security_token');
URL Handling
The adapter supports both public URLs and signed URLs. For Laravel's Storage
facade:
Public and Private URLs
The url()
method automatically handles both public and private objects:
// For public objects, returns a direct URL $publicUrl = Storage::disk('huawei-obs')->url('public-file.txt'); // Returns: https://obs.example.com/bucket/public-file.txt // For private objects, automatically returns a signed URL (1-hour expiration) $privateUrl = Storage::disk('huawei-obs')->url('private-file.txt'); // Returns: https://obs.example.com/bucket/private-file.txt?signature=...
Temporary URLs
Generate temporary URLs with custom expiration times:
// Generate a temporary URL that expires in 2 hours $tempUrl = Storage::disk('huawei-obs')->temporaryUrl( 'file.txt', now()->addHours(2) ); // Generate a temporary URL for uploads (PUT method) $uploadUrl = Storage::disk('huawei-obs')->temporaryUploadUrl( 'file.txt', now()->addHour() );
Object Tagging
Add and manage metadata tags on OBS objects:
// Set tags on an object Storage::disk('huawei-obs')->setObjectTags('file.txt', [ 'category' => 'images', 'processed' => 'true', 'user_id' => '123' ]); // Get tags from an object $tags = Storage::disk('huawei-obs')->getObjectTags('file.txt'); // Delete tags from an object Storage::disk('huawei-obs')->deleteObjectTags('file.txt');
Post Signatures
Create signatures for direct browser uploads to OBS:
// Generate a post signature for direct upload $signature = Storage::disk('huawei-obs')->createPostSignature('uploads/file.txt', [ 'success_action_status' => '201', 'x-amz-meta-category' => 'images' ], 3600); // 1 hour expiration // Use the signature in an HTML form echo '<form action="' . $signature['url'] . '" method="post" enctype="multipart/form-data">'; foreach ($signature['fields'] as $key => $value) { echo '<input type="hidden" name="' . $key . '" value="' . $value . '">'; } echo '<input type="file" name="file">'; echo '<input type="submit" value="Upload">'; echo '</form>';
Object Restoration
Restore archived objects from OBS:
// Restore an archived object (default 1 day) Storage::disk('huawei-obs')->restoreObject('archived-file.txt'); // Restore with custom restoration period (7 days) Storage::disk('huawei-obs')->restoreObject('archived-file.txt', 7);
Testing
Artisan Command
The package includes an Artisan command for testing connectivity and features:
php artisan huawei-obs:test
This command will:
- Validate credentials and bucket access
- Test reads/writes, signed URLs, post signatures, and tagging
- Output a concise pass/fail report
Manual Testing
You can also test the adapter manually:
use LaravelFlysystemHuaweiObs\HuaweiObsAdapter; $adapter = new HuaweiObsAdapter( 'your_access_key_id', 'your_secret_access_key', 'your_bucket_name', 'https://obs.cn-north-1.myhuaweicloud.com' ); // Test basic operations $adapter->write('test.txt', 'Hello World', new \League\Flysystem\Config()); $contents = $adapter->read('test.txt'); $adapter->delete('test.txt');
Error Handling
The package provides comprehensive error handling with clear, actionable error messages:
Common Error Scenarios
try { Storage::disk('huawei-obs')->put('file.txt', 'content'); } catch (\RuntimeException $e) { // Authentication errors if (str_contains($e->getMessage(), 'Authentication failed')) { // Check your credentials } // Bucket errors if (str_contains($e->getMessage(), 'NoSuchBucket')) { // Check your bucket name } // Permission errors if (str_contains($e->getMessage(), 'AccessDenied')) { // Check your IAM permissions } }
Custom Exceptions
The package provides specific exceptions for different error types used by advanced features:
use LaravelFlysystemHuaweiObs\Exceptions\UnableToCreateSignedUrl; use LaravelFlysystemHuaweiObs\Exceptions\UnableToSetObjectTags; try { $url = Storage::disk('huawei-obs')->temporaryUrl('file.txt', now()->addHour()); } catch (UnableToCreateSignedUrl $e) { // Handle signed URL creation errors } try { Storage::disk('huawei-obs')->setObjectTags('file.txt', ['tag' => 'value']); } catch (UnableToSetObjectTags $e) { // Handle object tagging errors }
Performance Considerations
Large Dataset Handling
For applications with large numbers of files, use the optimized methods:
// Instead of allFiles() which might timeout $allFiles = Storage::disk('huawei-obs')->allFiles(); // Use optimized method with limits $files = Storage::disk('huawei-obs')->allFilesOptimized(10000, 60);
Caching
Consider caching frequently accessed data:
// Cache file listings $files = Cache::remember('huawei-obs-files', 300, function () { return Storage::disk('huawei-obs')->files('uploads'); });
Batch Operations
For multiple operations, consider batching:
// Instead of multiple individual calls foreach ($files as $file) { Storage::disk('huawei-obs')->delete($file); } // Consider using background jobs for large batches
Troubleshooting
Common Issues
-
Authentication Errors
- Verify your access key and secret key
- Check if your security token is still valid
- Ensure your credentials have the necessary permissions
-
Bucket Access Errors
- Verify the bucket name is correct
- Check if the bucket exists in the specified region
- Ensure your credentials have access to the bucket
-
Timeout Errors
- Use optimized methods for large datasets
- Increase timeout values in configuration
- Consider using background processing for large operations
-
SSL Certificate Errors
- Verify your endpoint URL is correct
- Check if SSL verification is required in your environment
- Consider disabling SSL verification for testing (not recommended for production)
Debug Mode
Enable debug mode to get more detailed error information:
'huawei-obs' => [ 'logging_enabled' => true, 'log_operations' => true, 'log_errors' => true, // ... other config ],
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Development Setup
- Clone the repository
- Install dependencies:
composer install
- Run tests:
composer test
- Static analysis:
composer phpstan
- Lint/format:
composer pint
Code Quality
This package follows strict code quality standards:
- PHPStan Level 8: Maximum type safety
- Laravel Pint: PSR-12 coding standards
- PHPUnit: Comprehensive test coverage
- GitHub Actions: Automated CI/CD pipeline
License
This package is open-sourced software licensed under the MIT license.
Support
- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Changelog
Please see CHANGELOG.md for more information on what has changed recently.
Credits
- Author: Mubbasher Ahmed
- Maintainer: Mubbasher Ahmed
- License: MIT
Made with ❤️ by Mubbasher Ahmed