├── .github ├── ISSUE_TEMPLATE │ ├── Bug.md │ ├── Feature_Request.md │ └── Question.md ├── stale.yml └── workflows │ └── ci.yml ├── .gitignore ├── .php-cs-fixer.dist.php ├── .php-version ├── LICENSE ├── README.md ├── composer.json ├── doc ├── adapter_async_aws_s3.md ├── adapter_awss3.md ├── adapter_azure_blob_storage.md ├── adapter_custom.md ├── adapter_ftp.md ├── adapter_gitlab.md ├── adapter_google_cloud_storage.md ├── adapter_in_memory.md ├── adapter_local.md ├── adapter_sftp.md ├── filesystem_create.md ├── filesystem_php_config.md ├── index.md └── tests.md ├── phpstan.neon ├── phpunit.xml.dist ├── src ├── DependencyInjection │ ├── Compiler │ │ └── FilesystemPass.php │ ├── Configuration.php │ ├── Factory │ │ ├── Adapter │ │ │ ├── AsyncAwsS3Factory.php │ │ │ ├── AwsS3V3Factory.php │ │ │ ├── AzureBlobFactory.php │ │ │ ├── CustomAdapterFactory.php │ │ │ ├── FtpFactory.php │ │ │ ├── GitlabFactory.php │ │ │ ├── GoogleCloudStorageFactory.php │ │ │ ├── InMemoryFactory.php │ │ │ ├── LocalFactory.php │ │ │ └── SftpFactory.php │ │ ├── AdapterFactoryInterface.php │ │ └── FactoryInterface.php │ └── OneupFlysystemExtension.php ├── OneupFlysystemBundle.php └── Resources │ └── config │ ├── adapters.xml │ ├── factories.xml │ └── flysystem.xml └── tests ├── App ├── Kernel.php └── config │ └── config.yml ├── DependencyInjection ├── Compiler │ └── FilesystemPassTest.php └── OneupFlysystemExtensionTest.php └── bootstrap.php /.github/ISSUE_TEMPLATE/Bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug 3 | about: Did you encounter a bug? 4 | --- 5 | 6 | ### Bug Report 7 | 8 | 9 | 10 | | Q | A 11 | |------------ | ------ 12 | | BC Break | yes 13 | | Version | x.y.z 14 | 15 | #### Summary 16 | 17 | 18 | 19 | #### How to reproduce 20 | 21 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_Request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🎉 Feature Request 3 | about: Do you have a new feature in mind? 4 | --- 5 | 6 | ### Feature Request 7 | 8 | 9 | 10 | | Q | A 11 | |------------ | ------ 12 | | New Feature | yes 13 | | BC Break | yes/no 14 | 15 | #### Scenario 16 | 17 | 18 | 19 | #### Summary 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ❓ Question 3 | about: Are you unsure about something? 4 | --- 5 | 6 | ### Question 7 | 8 | 9 | 10 | | Q | A 11 | |------------ | ------ 12 | | Version | x.y.z 13 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - postponed 8 | - enhancement 9 | - bug 10 | - question 11 | - Easy Pick 12 | # Label to use when marking an issue as stale 13 | staleLabel: stale 14 | # Comment to post when marking an issue as stale. Set to `false` to disable 15 | markComment: > 16 | This issue has been automatically marked as stale because it has not had 17 | recent activity. It will be closed if no further activity occurs. Thank you 18 | for your contributions. 19 | # Comment to post when closing a stale issue. Set to `false` to disable 20 | closeComment: false 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: ~ 5 | push: 6 | branches: 7 | - main 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | coding-style: 13 | name: Coding Style 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@2.30.0 18 | with: 19 | php-version: 8.1 20 | extensions: dom, fileinfo, filter, gd, hash, intl, json, mbstring, pcre, pdo, zlib 21 | coverage: none 22 | 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Install the dependencies 27 | run: composer install --no-interaction --no-suggest 28 | - name: Check the coding style 29 | run: vendor/bin/php-cs-fixer fix --diff --dry-run 30 | - name: Analyze the code 31 | run: vendor/bin/phpstan analyze --no-progress 32 | 33 | tests: 34 | name: PHP ${{ matrix.php }} / SF ^${{ matrix.symfony }} 35 | runs-on: ubuntu-latest 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | php: [8.1, 8.2, 8.3, 8.4] 40 | symfony: [5.4, 6.0, 7.0] 41 | exclude: 42 | - symfony: 7.0 43 | php: 8.0 44 | - symfony: 7.0 45 | php: 8.1 46 | steps: 47 | - name: Setup PHP 48 | uses: shivammathur/setup-php@2.30.0 49 | with: 50 | php-version: ${{ matrix.php }} 51 | extensions: dom, fileinfo, filter, gd, hash, intl, json, mbstring, pcre, pdo_mysql, zlib 52 | coverage: none 53 | 54 | - name: Checkout 55 | uses: actions/checkout@v4 56 | 57 | - name: Install the dependencies 58 | run: | 59 | composer require symfony/framework-bundle:^${{ matrix.symfony }} symfony/translation:^${{ matrix.symfony }} symfony/console:^${{ matrix.symfony }} -W 60 | composer install --no-interaction --no-suggest 61 | - name: Run the unit tests 62 | run: vendor/bin/phpunit --colors=always 63 | 64 | prefer-lowest: 65 | name: Prefer Lowest 66 | runs-on: ubuntu-latest 67 | steps: 68 | - name: Setup PHP 69 | uses: shivammathur/setup-php@2.30.0 70 | with: 71 | php-version: 8.1 72 | extensions: dom, fileinfo, filter, gd, hash, intl, json, mbstring, pcre, pdo_mysql, zlib 73 | coverage: none 74 | 75 | - name: Checkout 76 | uses: actions/checkout@v4 77 | 78 | - name: Install the dependencies 79 | run: composer update --prefer-lowest --prefer-stable --no-interaction --no-suggest 80 | - name: Run the unit tests 81 | run: vendor/bin/phpunit --colors=always 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | phpunit.xml 3 | .phpunit.result.cache 4 | composer.lock 5 | vendor 6 | tests/App/cache 7 | tests/App/logs 8 | tests/App/var 9 | cache 10 | var/cache 11 | var/log 12 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([__DIR__ . '/src', __DIR__ . '/tests']) 5 | ->exclude(['App/cache', 'App/var']) 6 | ; 7 | 8 | return (new PhpCsFixer\Config()) 9 | ->setRules([ 10 | '@DoctrineAnnotation' => true, 11 | '@Symfony' => true, 12 | '@Symfony:risky' => true, 13 | '@PHP71Migration' => true, 14 | '@PHP71Migration:risky' => true, 15 | '@PHPUnit60Migration:risky' => true, 16 | '@PHPUnit75Migration:risky' => true, 17 | 'align_multiline_comment' => true, 18 | 'array_syntax' => ['syntax' => 'short'], 19 | 'concat_space' => [ 20 | 'spacing' => 'one', 21 | ], 22 | 'combine_consecutive_issets' => true, 23 | 'combine_consecutive_unsets' => true, 24 | 'general_phpdoc_annotation_remove' => [ 25 | 'annotations' => [ 26 | 'author', 27 | 'expectedException', 28 | 'expectedExceptionMessage', 29 | ], 30 | ], 31 | 'heredoc_to_nowdoc' => true, 32 | 'linebreak_after_opening_tag' => true, 33 | 'list_syntax' => ['syntax' => 'short'], 34 | 'no_superfluous_elseif' => true, 35 | 'no_unreachable_default_argument_value' => true, 36 | 'no_useless_else' => true, 37 | 'no_useless_return' => true, 38 | 'ordered_class_elements' => true, 39 | 'ordered_imports' => true, 40 | 'php_unit_strict' => true, 41 | 'phpdoc_add_missing_param_annotation' => true, 42 | 'phpdoc_order' => true, 43 | 'phpdoc_types_order' => [ 44 | 'null_adjustment' => 'always_last', 45 | 'sort_algorithm' => 'none', 46 | ], 47 | 'strict_comparison' => true, 48 | 'strict_param' => true, 49 | 'void_return' => true, 50 | ]) 51 | ->setFinder($finder) 52 | ->setRiskyAllowed(true) 53 | ->setUsingCache(false) 54 | ; 55 | -------------------------------------------------------------------------------- /.php-version: -------------------------------------------------------------------------------- 1 | 8.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 1up GmbH 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OneupFlysystemBundle 2 | ==================== 3 | 4 | [![Build Status](https://github.com/1up-lab/OneupFlysystemBundle/workflows/CI/badge.svg)](https://github.com/1up-lab/OneupFlysystemBundle/actions) 5 | [![Total Downloads](https://poser.pugx.org/oneup/flysystem-bundle/d/total.png)](https://packagist.org/packages/oneup/flysystem-bundle) 6 | 7 | **Now 4.x available with [Flysystem V2 & V3](https://flysystem.thephpleague.com/docs/what-is-new/) support!** 8 | 9 | The OneupFlysystemBundle provides a [Flysystem](https://github.com/thephpleague/flysystem) integration for your Symfony projects. Flysystem is a filesystem abstraction which allows you to easily swap out a local filesystem for a remote one. Currently you can configure the following adapters to use in your Symfony project. 10 | 11 | * [Google Cloud Storage](https://cloud.google.com/storage) 12 | * [AsyncAwsS3](https://async-aws.com/) 13 | * [AwsS3](https://aws.amazon.com/de/sdk-for-php/) 14 | * [AzureBlobStorage](https://azure.microsoft.com/en-us/services/storage/blobs/) 15 | * [Ftp](https://www.php.net/manual/en/book.ftp.php) 16 | * [Local filesystem](https://www.php.net/manual/en/ref.filesystem.php) 17 | * [Sftp](https://phpseclib.sourceforge.net/sftp/intro.html) 18 | 19 | Documentation 20 | ------------- 21 | 22 | The entry point of the documentation can be found in the file `doc/index.md` 23 | 24 | [Read the documentation for the latest release](doc/index.md) 25 | 26 | 27 | Flysystem 1.x 28 | ------------- 29 | If you're looking for Flysystem 1.x support, check out the [3.x-branch](https://github.com/1up-lab/OneupFlysystemBundle/tree/release/3.x) of this bundle. 30 | 31 | 32 | License 33 | ------- 34 | 35 | This bundle is under the MIT license. See the [complete license](LICENSE) in the bundle: 36 | 37 | Reporting an issue or a feature request 38 | --------------------------------------- 39 | 40 | Issues and feature requests are tracked in the [Github issue tracker](https://github.com/1up-lab/OneupFlysystemBundle/issues). 41 | 42 | When reporting a bug, it may be a good idea to reproduce it in a basic project 43 | built using the [symfony/website-skeleton](https://symfony.com/doc/current/setup.html#creating-symfony-applications) 44 | to allow developers of the bundle to reproduce the issue by simply cloning it 45 | and following some steps. 46 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oneup/flysystem-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Integrates Flysystem filesystem abstraction library to your Symfony project.", 5 | "keywords": [ 6 | "symfony", 7 | "flysystem", 8 | "filesystem", 9 | "abstraction" 10 | ], 11 | "homepage": "https://1up.io", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Jim Schmid", 16 | "email": "js@1up.io", 17 | "homepage": "https://1up.io", 18 | "role": "Developer" 19 | }, 20 | { 21 | "name": "David Greminger", 22 | "email": "dg@1up.io", 23 | "homepage": "https://1up.io", 24 | "role": "Developer" 25 | } 26 | ], 27 | "require": { 28 | "php": "^8.1", 29 | "league/flysystem": "^2.0 || ^3.0", 30 | "symfony/config": "^5.4 || ^6.0 || ^7.0", 31 | "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", 32 | "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0" 33 | }, 34 | "require-dev": { 35 | "ext-simplexml": "*", 36 | "friendsofphp/php-cs-fixer": "^3.51", 37 | "league/flysystem-async-aws-s3": "^2.0 || ^3.0", 38 | "league/flysystem-aws-s3-v3": "^2.0 || ^3.0", 39 | "league/flysystem-ftp": "^2.0 || ^3.0", 40 | "league/flysystem-google-cloud-storage": "^2.0 || ^3.0", 41 | "league/flysystem-memory": "^2.0 || ^3.0", 42 | "league/flysystem-sftp-v3": "^2.0 || ^3.0", 43 | "league/flysystem-azure-blob-storage": "^3.0", 44 | "phpstan/phpstan": "^1.10", 45 | "phpunit/phpunit": "^9.6.17", 46 | "royvoetman/flysystem-gitlab-storage": "^2.0 || ^3.0", 47 | "symfony/asset": "^5.4 || ^6.0 || ^7.0", 48 | "symfony/browser-kit": "^5.4 || ^6.0 || ^7.0", 49 | "symfony/finder": "^5.4 || ^6.0 || ^7.0", 50 | "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", 51 | "symfony/phpunit-bridge": "^7.0", 52 | "symfony/translation": "^5.4 || ^6.0 || ^7.0", 53 | "symfony/yaml": "^5.4 || ^6.0 || ^7.0" 54 | }, 55 | "suggest": { 56 | "ext-fileinfo": "Required for MimeType", 57 | "ext-ftp": "Required for FTP and SFTP", 58 | "league/flysystem-async-aws-s3": "Use flysystem S3 adapter from AsyncAws", 59 | "league/flysystem-aws-s3-v3": "Use S3 storage with AWS SDK v3", 60 | "league/flysystem-google-cloud-storage": "Use Google Cloud Storage Adapter for Flysystem", 61 | "league/flysystem-sftp-v3": "Allows SFTP server storage via phpseclib", 62 | "royvoetman/flysystem-gitlab-storage": "Use Gitlab Storage filesystem for Flysystem" 63 | }, 64 | "config": { 65 | "sort-packages": true 66 | }, 67 | "autoload": { 68 | "psr-4": { 69 | "Oneup\\FlysystemBundle\\": "src" 70 | } 71 | }, 72 | "autoload-dev": { 73 | "psr-4": { 74 | "Oneup\\FlysystemBundle\\Tests\\": "tests" 75 | }, 76 | "classmap": [ 77 | "tests/App/Kernel.php" 78 | ] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /doc/adapter_async_aws_s3.md: -------------------------------------------------------------------------------- 1 | # Use the AsyncAwsS3 adapter 2 | 3 | In order to use the AsyncAwsS3 adapter, you first need to create a S3 client service. 4 | This can be done with the following configuration. Read more about instantiating 5 | the S3 client at [async-aws.com](https://async-aws.com/clients/) or use the 6 | [AsyncAws SymfonyBundle](https://async-aws.com/integration/symfony-bundle.html). 7 | 8 | ```yml 9 | services: 10 | acme.async.portable_visibility_converter: 11 | class: League\Flysystem\AsyncAwsS3\PortableVisibilityConverter 12 | 13 | acme.async_s3_client: 14 | class: AsyncAws\S3\S3Client 15 | arguments: 16 | - region: 'eu-central-1' 17 | accessKeyId: 'AKIAIOSFODNN7EXAMPLE' 18 | accessKeySecret: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' 19 | ``` 20 | 21 | Set this service as the value of the `client` key in the `oneup_flysystem` configuration. 22 | 23 | ```yml 24 | oneup_flysystem: 25 | adapters: 26 | acme.flysystem_adapter: 27 | async_aws_s3: 28 | client: acme.async_s3_client 29 | bucket: 'my_image_bucket' 30 | prefix: '' 31 | visibilityConverter: acme.async.portable_visibility_converter 32 | 33 | ``` 34 | 35 | ## More to know 36 | 37 | * [Create and use your filesystem](filesystem_create.md) 38 | -------------------------------------------------------------------------------- /doc/adapter_awss3.md: -------------------------------------------------------------------------------- 1 | # Use the AwsS3 adapter 2 | 3 | There are two AwsS3 adapters that you can use with this bundle. Below is the configuration for AwsS3v3 and AwsS3v2 adapters. 4 | 5 | ## AwsS3v3 6 | 7 | In order to use the AwsS3v3 adapter, you first need to create 8 | a client object defined as a service. This Flysystem adapter 9 | works with the [aws/aws-sdk-php](https://packagist.org/packages/aws/aws-sdk-php) package version 3 and above. 10 | This version requires you to use the "v4" of the signature. 11 | 12 | ```yml 13 | services: 14 | acme.awss3v3.portable_visibility_converter: 15 | class: League\Flysystem\AwsS3V3\PortableVisibilityConverter 16 | 17 | acme.s3_client: 18 | class: Aws\S3\S3Client 19 | arguments: 20 | - 21 | version: '2006-03-01' # or 'latest' 22 | region: "region-id" # 'eu-central-1' for example 23 | credentials: 24 | key: "s3-key" 25 | secret: "s3-secret" 26 | ``` 27 | 28 | Set this service as the value of the `client` key in the `oneup_flysystem` configuration. 29 | 30 | ```yml 31 | oneup_flysystem: 32 | adapters: 33 | acme.flysystem_adapter: 34 | awss3v3: 35 | client: acme.s3_client 36 | bucket: 'bucket-name' 37 | prefix: 'path/prefix' # Optional path prefix, you can set empty string 38 | visibilityConverter: acme.awss3v3.portable_visibility_converter 39 | ``` 40 | 41 | For more details on the other parameters, take a look at the [Flysystem documentation](https://flysystem.thephpleague.com/v2/docs/adapter/aws-s3-v3/). 42 | 43 | ## Additional parameters 44 | Use `options` config to pass additional parameters to AWS. 45 | 46 | ### Example use case 47 | 48 | Sending files to S3 bucket owned by another AWS account. 49 | In this situation default `ACL` value, which is `private`, prevents bucket owner from viewing the file. 50 | 51 | To allow bucket owner full control over an object use below config example: 52 | 53 | ```yml 54 | oneup_flysystem: 55 | adapters: 56 | acme.flysystem_adapter: 57 | awss3v3: 58 | client: acme.s3_client 59 | bucket: 'bucket-name' 60 | prefix: 'path/prefix' # Optional path prefix, you can set empty string 61 | options: 62 | ACL: bucket-owner-full-control 63 | ``` 64 | 65 | ## More to know 66 | * [Create and use your filesystem](filesystem_create.md) 67 | -------------------------------------------------------------------------------- /doc/adapter_azure_blob_storage.md: -------------------------------------------------------------------------------- 1 | # Use the Azure Blob Storage adapter 2 | 3 | This adapter connects to the filesystem in the Azure Blob Storage. 4 | 5 | 6 | ```yml 7 | oneup_flysystem: 8 | adapters: 9 | acme.flysystem_adapter: 10 | azureblob: 11 | client: 'azure_blob_storage_client' # Service ID of the MicrosoftAzure\Storage\Blob\BlobRestProxy 12 | container: 'container-name' 13 | prefix: 'optional/prefix' 14 | ``` 15 | 16 | For more details on the other parameters, take a look at the [Flysystem documentation](https://flysystem.thephpleague.com/docs/adapter/azure-blob-storage/). 17 | 18 | ## More to know 19 | 20 | * [Create and use your filesystem](filesystem_create.md) 21 | -------------------------------------------------------------------------------- /doc/adapter_custom.md: -------------------------------------------------------------------------------- 1 | # Use the Custom adapter 2 | 3 | In order to use a custom adapter, you first need to create 4 | a service implementing the `League\Flysystem\AdapterInterface`. 5 | 6 | Set this service as the value of the `service` key in the `oneup_flysystem` configuration. 7 | 8 | ```yml 9 | oneup_flysystem: 10 | adapters: 11 | acme.flysystem_adapter: 12 | custom: 13 | service: my_flysystem_service 14 | ``` 15 | 16 | ## More to know 17 | * [Create and use your filesystem](filesystem_create.md) 18 | -------------------------------------------------------------------------------- /doc/adapter_ftp.md: -------------------------------------------------------------------------------- 1 | # Use the FTP adapter 2 | 3 | This adapter works with the standard PHP FTP implementation which is documented in [the manual](http://www.php.net/manual/en/book.ftp.php). 4 | You have to provide at least a value for the `host` key. 5 | 6 | ```yml 7 | # app/config/config.yml 8 | oneup_flysystem: 9 | acme.ftp.portable_visibility_converter: 10 | class: League\Flysystem\UnixVisibility\PortableVisibilityConverter 11 | 12 | adapters: 13 | my_adapter: 14 | ftp: 15 | options: 16 | host: 'ftp.hostname.com' # required 17 | root: '/upload' # required 18 | username: 'username' # required 19 | password: 'password' # required 20 | visibilityConverter: acme.ftp.portable_visibility_converter 21 | ``` 22 | 23 | For more details on the other parameters, take a look at the [Flysystem documentation](https://flysystem.thephpleague.com/v2/docs/adapter/ftp/). 24 | 25 | ## More to know 26 | * [Create and use your filesystem](filesystem_create.md) 27 | -------------------------------------------------------------------------------- /doc/adapter_gitlab.md: -------------------------------------------------------------------------------- 1 | # Use the Gitlab adapter 2 | 3 | In order to use the Gitlab adapter, you first need to create a Gitlab client service. 4 | This can be done with the following configuration. Read more about instantiating 5 | the Gitlab client at [github.com/RoyVoetman/flysystem-gitlab-storage](https://github.com/RoyVoetman/flysystem-gitlab-storage#usage). 6 | 7 | ```yml 8 | services: 9 | acme.gitlab_client: 10 | class: RoyVoetman\FlysystemGitlab\Client 11 | arguments: 12 | - 'project-id' 13 | - 'branch' 14 | - 'base-url' 15 | - 'personal-access-token' 16 | ``` 17 | 18 | Set this service as the value of the `client` key in the `oneup_flysystem` configuration. 19 | 20 | ```yml 21 | oneup_flysystem: 22 | adapters: 23 | acme.gitlab_adapter: 24 | gitlab: 25 | client: acme.gitlab_client 26 | prefix: 'optional/path/prefix' 27 | ``` 28 | 29 | ## More to know 30 | 31 | * [Create and use your filesystem](filesystem_create.md) 32 | -------------------------------------------------------------------------------- /doc/adapter_google_cloud_storage.md: -------------------------------------------------------------------------------- 1 | # Use the Google Cloud Storage adapter 2 | 3 | This adapter connects to the filesystem in the Google Cloud Storage. 4 | 5 | 6 | ```yml 7 | oneup_flysystem: 8 | adapters: 9 | acme.flysystem_adapter: 10 | googlecloudstorage: 11 | client: 'google_cloud_storage_client' # Service ID of the Google\Cloud\Storage\StorageClient 12 | bucket: 'my_gcs_bucket' 13 | prefix: '' 14 | ``` 15 | 16 | For more details on the other parameters, take a look at the [Flysystem documentation](https://flysystem.thephpleague.com/docs/adapter/google-cloud-storage/). 17 | 18 | ## More to know 19 | 20 | * [Create and use your filesystem](filesystem_create.md) 21 | -------------------------------------------------------------------------------- /doc/adapter_in_memory.md: -------------------------------------------------------------------------------- 1 | # Use the MemoryAdapter 2 | 3 | This adapter keeps the filesystem solely in the memory. 4 | 5 | ```yml 6 | oneup_flysystem: 7 | adapters: 8 | memory_adapter: 9 | memory: ~ 10 | ``` 11 | 12 | For more details on the other parameters, take a look at the [Flysystem documentation](https://flysystem.thephpleague.com/v2/docs/adapter/in-memory/). 13 | 14 | ## More to know 15 | * [Create and use your filesystem](filesystem_create.md) 16 | -------------------------------------------------------------------------------- /doc/adapter_local.md: -------------------------------------------------------------------------------- 1 | # Use the local adapter 2 | 3 | To use the local adapter which stores files on the same server the Symfony instance runs, you have 4 | to provide a directory. 5 | 6 | ```yml 7 | # app/config/config.yml 8 | oneup_flysystem: 9 | adapters: 10 | my_adapter: 11 | local: 12 | location: "%kernel.root_dir%/../uploads" 13 | lazy: ~ # boolean (default "false") 14 | writeFlags: ~ 15 | linkHandling: ~ 16 | permissions: 17 | file: 18 | public: 0o644 19 | private: 0o600 20 | dir: 21 | public: 0o755 22 | private: 0o700 23 | lazyRootCreation: ~ # boolean (default "false") 24 | ``` 25 | 26 | For more details on the `lazy` parameter, take a look at the [Symfony documentation](http://symfony.com/doc/current/components/dependency_injection/lazy_services.html). 27 | For the other parameters, take a look at the [Flysystem documentation](https://flysystem.thephpleague.com/v2/docs/adapter/local/). 28 | 29 | ## More to know 30 | * [Create and use your filesystem](filesystem_create.md) 31 | -------------------------------------------------------------------------------- /doc/adapter_sftp.md: -------------------------------------------------------------------------------- 1 | # Use the SFTP adapter 2 | 3 | You have to provide at least a value for the `host` key. 4 | 5 | ```yml 6 | # app/config/config.yml 7 | oneup_flysystem: 8 | adapters: 9 | my_adapter: 10 | sftp: 11 | options: 12 | host: 'ftp.domain.com' 13 | username: 'foo' 14 | root: '/upload' 15 | ``` 16 | 17 | For more details on the other parameters, take a look at the [Flysystem documentation](https://flysystem.thephpleague.com/docs/adapter/sftp-v3/). 18 | 19 | ## More to know 20 | * [Create and use your filesystem](filesystem_create.md) 21 | -------------------------------------------------------------------------------- /doc/filesystem_create.md: -------------------------------------------------------------------------------- 1 | # Create and use your filesystems 2 | 3 | After successfully configured the adapters, create a filesystem and inject an adapter of your choice. 4 | 5 | ```yml 6 | oneup_flysystem: 7 | adapter: ~ 8 | filesystems: 9 | acme: 10 | adapter: my_adapter 11 | alias: ~ 12 | mount: ~ 13 | visibility: ~ 14 | directory_visibility: ~ 15 | ``` 16 | 17 | This will expose a new service for you to use. 18 | 19 | ```php 20 | $filesystem = $container->get('oneup_flysystem.acme_filesystem'); 21 | ``` 22 | 23 | The naming scheme follows a simple rule: `oneup_flysystem.%s_filesystem` whereas `%s` is the name (config key) of your filesystem. 24 | 25 | The `$filesystem` variable is of the type [`\League\Flysystem\Filesystem`](https://github.com/thephpleague/flysystem/blob/master/src/Filesystem.php). 26 | Please refer to the [*General Usage* section](http://flysystem.thephpleague.com/api/#general-usage) in the official documentation for details. 27 | 28 | ## Alias your filesystem 29 | 30 | You can alias your filesystem by providing an `alias` key. 31 | 32 | ```yml 33 | oneup_flysystem: 34 | adapter: ~ 35 | filesystems: 36 | acme: 37 | adapter: my_adapter 38 | alias: acme_filesystem 39 | ``` 40 | Afterwards, the filesystem service is aliased with the provided value and can be retrieved like this: 41 | 42 | ```php 43 | $filesystem = $container->get('acme_filesystem'); 44 | ``` 45 | 46 | ## Inject your filesystem in your services 47 | 48 | If you use Dependency Injection instead of the container as service locator, you can inject your filesystems in your services: 49 | 50 | ```yml 51 | services: 52 | app.my_service: 53 | class: App\MyService 54 | arguments: 55 | - '@oneup_flysystem.acme_filesystem' # Inject the resolved service name, or the alias (see previous section) 56 | ``` 57 | 58 | Dependency Injection is considered a **good practice** since you do not need the container, and you can then refer to it in your class like this: 59 | 60 | ```php 61 | 62 | use League\Flysystem\FilesystemOperator; 63 | 64 | class MyService 65 | { 66 | public function __construct(FilesystemOperator $acmeFilesystem) 67 | { 68 | $this->filesystem = $acmeFilesystem; 69 | } 70 | } 71 | ``` 72 | 73 | 💡 Pro tip: when using **Symfony 4.2**, you can completely omit the service arguments definition in your config files, 74 | and instead you can automatically inject your filesystems by **providing the exact same type-hint as above**, and 75 | replace `acme` with the name of your filesystem. Thanks to the ``ContainerBuilder::registerAliasForArgument()`` method! 76 | 77 | ## Use the Mount Manager 78 | 79 | Details on the usage of the MountManager can be found in the [Flysystem documentation](https://flysystem.thephpleague.com/docs/advanced/mount-manager/). 80 | 81 | ## Add caching 82 | 83 | In version 1.x of Flysystem you could provide a cache per each adapter. [The cached adapter was not ported to V2 of Flysystem](https://flysystem.thephpleague.com/docs/upgrade-from-1.x/#miscellaneous-changes). 84 | 85 | If you want to use cached adapters, give a try to [lustmored/flysystem-v2-simple-cache-adapter](https://github.com/Lustmored/flysystem-v2-simple-cache-adapter). 86 | -------------------------------------------------------------------------------- /doc/filesystem_php_config.md: -------------------------------------------------------------------------------- 1 | # Config based on PHP files 2 | 3 | If you're using Symfony 5 and want to configure this bundle with a PHP file instead of a YAML, 4 | you can set up the `config/packages/oneup_flysystem.php` file like this: 5 | 6 | ```php 7 | 8 | extension('oneup_flysystem', [ 15 | 'adapters' => [ 16 | 'my_adapter' => [ 17 | 'local' => [ 18 | 'directory' => '%kernel.root_dir%/cache' 19 | ] 20 | ] 21 | ], 22 | 'filesystems' => [ 23 | 'my_filesystem' => [ 24 | 'adapter' => 'my_adapter', 25 | 'visibility' => 'private' 26 | 'directory_visibility' => 'private' 27 | ] 28 | ] 29 | ]); 30 | }; 31 | ``` 32 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | The OneupFlysystemBundle was developed and tested for Symfony version 4.4+. 4 | 5 | ## Installation 6 | Perform the following steps to install and use the basic functionality of the OneupFlysystemBundle: 7 | 8 | * Download OneupFlysystemBundle using Composer 9 | * Enable the bundle 10 | * Configure your filesystems 11 | 12 | ### Step 1: Download the bundle 13 | 14 | Download the bundle via composer: 15 | 16 | ```sh 17 | composer require oneup/flysystem-bundle 18 | ``` 19 | 20 | Composer will now fetch and install this bundle in the vendor directory `vendor/oneup` 21 | 22 | **Note**: There are some additional dependencies you will need to install for some features: 23 | 24 | * The AwsS3v3 adapter requires `"league/flysystem-aws-s3-v3"` 25 | * The FTP adapter requires `"league/flysystem-ftp"` 26 | * The SFTP (V3) adapter requires `"league/flysystem-sftp-v3"` 27 | * The Google Cloud Storage adapter requires `"league/flysystem-google-cloud-storage"` 28 | * The InMemory adapter requires `"league/flysystem-memory"` 29 | * The AsyncAwsS3 adapter requires `"league/flysystem-async-aws-s3"` 30 | * The Gitlab adapter requires `"royvoetman/flysystem-gitlab-storage"` 31 | * The Azure Blob Storage adapter requires `"league/flysystem-azure-blob-storage"` 32 | 33 | ### Step 2: Enable the bundle 34 | Enable the bundle in the kernel: 35 | 36 | ``` php 37 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/DependencyInjection/Compiler/FilesystemPass.php: -------------------------------------------------------------------------------- 1 | hasDefinition('oneup_flysystem.mount_manager')) { 16 | return; 17 | } 18 | 19 | $mountManager = $container->getDefinition('oneup_flysystem.mount_manager'); 20 | $configuredFilesystems = $container->findTaggedServiceIds('oneup_flysystem.filesystem'); 21 | $filesystems = []; 22 | 23 | foreach ($configuredFilesystems as $id => $attributes) { 24 | $filesystems[$attributes[0]['mount'] ?? $attributes[0]['key'] ?? $id] = new Reference($id); 25 | } 26 | 27 | $mountManager->replaceArgument(0, $filesystems); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | getRootNode(); 22 | 23 | $this->addAdapterSection($rootNode); 24 | $this->addFilesystemSection($rootNode); 25 | 26 | $rootNode 27 | ->children() 28 | ->end() 29 | ; 30 | 31 | return $treeBuilder; 32 | } 33 | 34 | private function addAdapterSection(ArrayNodeDefinition $node): void 35 | { 36 | $adapterNodeBuilder = $node 37 | ->fixXmlConfig('adapter') 38 | ->children() 39 | ->arrayNode('adapters') 40 | ->useAttributeAsKey('name') 41 | ->prototype('array') 42 | ->performNoDeepMerging() 43 | ->children() 44 | ; 45 | 46 | foreach ($this->adapterFactories as $name => $factory) { 47 | $factoryNode = $adapterNodeBuilder->arrayNode($name)->canBeUnset(); 48 | 49 | $factory->addConfiguration($factoryNode); 50 | } 51 | } 52 | 53 | private function addFilesystemSection(ArrayNodeDefinition $node): void 54 | { 55 | $supportedVisibilities = [ 56 | Visibility::PRIVATE, 57 | Visibility::PUBLIC, 58 | 'noPredefinedVisibility', 59 | ]; 60 | 61 | $node 62 | ->fixXmlConfig('filesystem') 63 | ->children() 64 | ->arrayNode('filesystems') 65 | ->useAttributeAsKey('name') 66 | ->prototype('array') 67 | ->children() 68 | ->scalarNode('adapter')->isRequired()->end() 69 | ->scalarNode('alias')->defaultNull()->end() 70 | ->scalarNode('mount')->defaultNull()->end() 71 | ->scalarNode('visibility') 72 | ->validate() 73 | ->ifNotInArray($supportedVisibilities) 74 | ->thenInvalid('The visibility %s is not supported.') 75 | ->end() 76 | ->end() 77 | ->scalarNode('directory_visibility') 78 | ->validate() 79 | ->ifNotInArray($supportedVisibilities) 80 | ->thenInvalid('The visibility %s is not supported.') 81 | ->end() 82 | ->end() 83 | ->end() 84 | ->end() 85 | ->end() 86 | ; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/AsyncAwsS3Factory.php: -------------------------------------------------------------------------------- 1 | setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.async_aws_s3')) 26 | ->replaceArgument(0, new Reference($config['client'])) 27 | ->replaceArgument(1, $config['bucket']) 28 | ->replaceArgument(2, $config['prefix']) 29 | ->replaceArgument(3, $visibilityConverter) 30 | ; 31 | } 32 | 33 | public function addConfiguration(NodeDefinition $node): void 34 | { 35 | $node 36 | ->children() 37 | ->scalarNode('client')->isRequired()->end() 38 | ->scalarNode('bucket')->isRequired()->end() 39 | ->scalarNode('prefix')->treatNullLike('')->defaultValue('')->end() 40 | ->scalarNode('visibilityConverter')->defaultNull()->end() 41 | ->end() 42 | ; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/AwsS3V3Factory.php: -------------------------------------------------------------------------------- 1 | setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.awss3v3')) 26 | ->replaceArgument(0, new Reference($config['client'])) 27 | ->replaceArgument(1, $config['bucket']) 28 | ->replaceArgument(2, $config['prefix']) 29 | ->replaceArgument(3, $visibilityConverter) 30 | ->replaceArgument(4, $config['mimeTypeDetector']) 31 | ->replaceArgument(5, (array) $config['options']) 32 | ->replaceArgument(6, $config['streamReads']) 33 | ; 34 | } 35 | 36 | public function addConfiguration(NodeDefinition $node): void 37 | { 38 | $node 39 | ->children() 40 | ->scalarNode('client')->isRequired()->end() 41 | ->scalarNode('bucket')->isRequired()->end() 42 | ->scalarNode('prefix')->defaultValue('')->end() 43 | ->scalarNode('visibilityConverter')->defaultNull()->end() 44 | ->scalarNode('mimeTypeDetector')->defaultNull()->end() 45 | ->arrayNode('options') 46 | ->scalarPrototype()->end() 47 | ->end() 48 | ->booleanNode('streamReads')->defaultTrue()->end() 49 | ->end() 50 | ; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/AzureBlobFactory.php: -------------------------------------------------------------------------------- 1 | setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.azureblob')) 24 | ->replaceArgument(0, new Reference($config['client'])) 25 | ->replaceArgument(1, $config['container']) 26 | ->replaceArgument(2, $config['prefix']) 27 | ; 28 | } 29 | 30 | public function addConfiguration(NodeDefinition $node): void 31 | { 32 | $node 33 | ->children() 34 | ->scalarNode('client')->isRequired()->end() 35 | ->scalarNode('container')->isRequired()->end() 36 | ->scalarNode('prefix')->defaultNull()->end() 37 | ->end() 38 | ; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/CustomAdapterFactory.php: -------------------------------------------------------------------------------- 1 | setAlias($id, $config['service']); 21 | } 22 | 23 | public function addConfiguration(NodeDefinition $node): void 24 | { 25 | $node 26 | ->children() 27 | ->variableNode('service')->isRequired()->cannotBeEmpty()->end() 28 | ->end() 29 | ; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/FtpFactory.php: -------------------------------------------------------------------------------- 1 | setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.ftp')) 28 | ->replaceArgument(0, 29 | (new Definition(FtpConnectionOptions::class)) 30 | ->setFactory([FtpConnectionOptions::class, 'fromArray']) 31 | ->addArgument($config['options']) 32 | ->setShared(false) 33 | ) 34 | ->replaceArgument(1, $config['connectionProvider']) 35 | ->replaceArgument(2, $config['connectivityChecker']) 36 | ->replaceArgument(3, $visibilityConverter) 37 | ->replaceArgument(4, $config['mimeTypeDetector']) 38 | ; 39 | } 40 | 41 | public function addConfiguration(NodeDefinition $node): void 42 | { 43 | $node 44 | ->children() 45 | ->arrayNode('options') 46 | ->children() 47 | ->scalarNode('host')->isRequired()->end() 48 | ->scalarNode('root')->isRequired()->end() 49 | ->scalarNode('username')->isRequired()->end() 50 | ->scalarNode('password')->isRequired()->end() 51 | ->scalarNode('port')->defaultValue(21)->end() 52 | ->booleanNode('ssl')->defaultFalse()->end() 53 | ->scalarNode('timeout')->defaultValue(90)->end() 54 | ->booleanNode('utf8')->defaultFalse()->end() 55 | ->booleanNode('passive')->defaultTrue()->end() 56 | ->scalarNode('transferMode')->defaultValue(\defined('FTP_BINARY') ? \FTP_BINARY : null)->end() 57 | ->scalarNode('systemType')->defaultNull()->end() 58 | ->booleanNode('ignorePassiveAddress')->defaultNull()->end() 59 | ->booleanNode('timestampsOnUnixListingsEnabled')->defaultFalse()->end() 60 | ->booleanNode('recurseManually')->defaultFalse()->end() 61 | ->booleanNode('useRawListOptions')->defaultFalse()->end() 62 | ->end() 63 | ->end() 64 | ->scalarNode('connectionProvider')->defaultNull()->end() 65 | ->scalarNode('connectivityChecker')->defaultNull()->end() 66 | ->scalarNode('visibilityConverter')->defaultNull()->end() 67 | ->scalarNode('mimeTypeDetector')->defaultNull()->end() 68 | ->end() 69 | ; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/GitlabFactory.php: -------------------------------------------------------------------------------- 1 | setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.gitlab')) 24 | ->replaceArgument(0, new Reference($config['client'])) 25 | ->replaceArgument(1, $config['prefix']) 26 | ; 27 | } 28 | 29 | public function addConfiguration(NodeDefinition $node): void 30 | { 31 | $node 32 | ->children() 33 | ->scalarNode('client')->isRequired()->end() 34 | ->scalarNode('prefix')->treatNullLike('')->defaultValue('')->end() 35 | ->end() 36 | ; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/GoogleCloudStorageFactory.php: -------------------------------------------------------------------------------- 1 | setFactory([new Reference($config['client']), 'bucket']); 26 | $bucket->setArgument(0, $config['bucket']); 27 | 28 | $visibilityHandler = $config['visibilityHandler'] ? new Definition($config['visibilityHandler']) : null; 29 | 30 | $container 31 | ->setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.googlecloudstorage')) 32 | ->replaceArgument(0, $bucket) 33 | ->replaceArgument(1, $config['prefix']) 34 | ->replaceArgument(2, $visibilityHandler) 35 | ->replaceArgument(3, $config['defaultVisibility']) 36 | ->replaceArgument(4, $config['mimeTypeDetector']) 37 | ; 38 | } 39 | 40 | public function addConfiguration(NodeDefinition $node): void 41 | { 42 | $node 43 | ->children() 44 | ->scalarNode('client')->isRequired()->end() 45 | ->scalarNode('bucket')->isRequired()->end() 46 | ->scalarNode('prefix')->treatNullLike('')->defaultValue('')->end() 47 | ->scalarNode('visibilityHandler')->defaultNull()->end() 48 | ->scalarNode('defaultVisibility')->defaultValue(Visibility::PRIVATE)->end() 49 | ->scalarNode('mimeTypeDetector')->defaultNull()->end() 50 | ->end() 51 | ; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/InMemoryFactory.php: -------------------------------------------------------------------------------- 1 | setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.memory')) 24 | ->replaceArgument(0, $config['defaultVisibility']) 25 | ; 26 | } 27 | 28 | public function addConfiguration(NodeDefinition $node): void 29 | { 30 | $node 31 | ->children() 32 | ->scalarNode('defaultVisibility')->defaultValue(Visibility::PUBLIC)->end() 33 | ->end() 34 | ; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/LocalFactory.php: -------------------------------------------------------------------------------- 1 | setFactory([PortableVisibilityConverter::class, 'fromArray']); 29 | $visibilityConverter->setArgument(0, $config['permissions']); 30 | } 31 | 32 | $container 33 | ->setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.local')) 34 | ->setLazy($config['lazy']) 35 | ->replaceArgument(0, $config['location']) 36 | ->replaceArgument(1, $visibilityConverter) 37 | ->replaceArgument(2, $config['writeFlags']) 38 | ->replaceArgument(3, $config['linkHandling']) 39 | ->replaceArgument(4, $config['mimeTypeDetector']) 40 | ->replaceArgument(5, $config['lazyRootCreation']) 41 | ; 42 | } 43 | 44 | public function addConfiguration(NodeDefinition $node): void 45 | { 46 | $parseOctal = \Closure::fromCallable([self::class, 'parseOctal']); 47 | 48 | $node 49 | ->children() 50 | ->booleanNode('lazy')->defaultValue(false)->end() 51 | ->scalarNode('location')->isRequired()->end() 52 | ->arrayNode('permissions') 53 | ->children() 54 | ->arrayNode('file') 55 | ->children() 56 | ->integerNode('public') 57 | ->beforeNormalization() 58 | ->ifString() 59 | ->then($parseOctal) 60 | ->end() 61 | ->defaultNull() 62 | ->end() 63 | ->integerNode('private') 64 | ->beforeNormalization() 65 | ->ifString() 66 | ->then($parseOctal) 67 | ->end() 68 | ->defaultNull() 69 | ->end() 70 | ->end() 71 | ->end() 72 | ->arrayNode('dir') 73 | ->children() 74 | ->integerNode('public') 75 | ->beforeNormalization() 76 | ->ifString() 77 | ->then($parseOctal) 78 | ->end() 79 | ->defaultNull() 80 | ->end() 81 | ->integerNode('private') 82 | ->beforeNormalization() 83 | ->ifString() 84 | ->then($parseOctal) 85 | ->end() 86 | ->defaultNull() 87 | ->end() 88 | ->end() 89 | ->end() 90 | ->end() 91 | ->end() 92 | ->scalarNode('writeFlags')->defaultValue(\LOCK_EX)->end() 93 | ->scalarNode('linkHandling')->defaultValue(LocalFilesystemAdapter::DISALLOW_LINKS)->end() 94 | ->scalarNode('mimeTypeDetector')->defaultNull()->end() 95 | ->scalarNode('lazyRootCreation')->defaultValue(false)->end() 96 | ->end() 97 | ; 98 | } 99 | 100 | /** 101 | * Backward compatibility (BC) between symfony/yaml <= 5.4 and >= 6.0. 102 | * 103 | * @see https://github.com/symfony/symfony/pull/34813 104 | */ 105 | private static function parseOctal(string $scalar): int 106 | { 107 | if (!preg_match('/^(?:\+|-)?0o?(?P[0-7_]++)$/', $scalar, $matches)) { 108 | throw new \InvalidArgumentException("The scalar \"$scalar\" is not a valid octal number."); 109 | } 110 | 111 | $value = str_replace('_', '', $matches['value']); 112 | 113 | if ('-' === $scalar[0]) { 114 | return (int) -octdec($value); 115 | } 116 | 117 | return (int) octdec($value); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/Adapter/SftpFactory.php: -------------------------------------------------------------------------------- 1 | setFactory([PortableVisibilityConverter::class, 'fromArray']); 37 | $visibilityConverter->setArgument(0, $config['permissions']); 38 | } 39 | 40 | $container 41 | ->setDefinition($id, new ChildDefinition('oneup_flysystem.adapter.sftp')) 42 | ->replaceArgument(0, (new Definition(SftpConnectionProvider::class)) 43 | ->setFactory([SftpConnectionProvider::class, 'fromArray']) 44 | ->addArgument($config['options']) 45 | ->setShared(false) 46 | ) 47 | ->replaceArgument(1, $root) 48 | ->replaceArgument(2, $visibilityConverter) 49 | ->replaceArgument(3, $config['mimeTypeDetector']) 50 | ; 51 | } 52 | 53 | public function addConfiguration(NodeDefinition $node): void 54 | { 55 | $parseOctal = \Closure::fromCallable([self::class, 'parseOctal']); 56 | 57 | $node 58 | ->children() 59 | ->arrayNode('options')->isRequired() 60 | ->children() 61 | ->scalarNode('host')->isRequired()->end() 62 | ->scalarNode('username')->isRequired()->end() 63 | ->scalarNode('password')->defaultNull()->end() 64 | ->scalarNode('privateKey')->defaultNull()->end() 65 | ->scalarNode('passphrase')->defaultNull()->end() 66 | ->scalarNode('port')->defaultValue(22)->end() 67 | ->booleanNode('useAgent')->defaultFalse()->end() 68 | ->scalarNode('timeout')->defaultValue(10)->end() 69 | ->scalarNode('maxTries')->defaultValue(4)->end() 70 | ->scalarNode('hostFingerprint')->defaultNull()->end() 71 | ->scalarNode('connectivityChecker')->defaultNull()->end() 72 | ->scalarNode('root')->isRequired()->end() 73 | ->end() 74 | ->end() 75 | ->arrayNode('permissions') 76 | ->children() 77 | ->arrayNode('file') 78 | ->children() 79 | ->integerNode('public') 80 | ->beforeNormalization() 81 | ->ifString() 82 | ->then($parseOctal) 83 | ->end() 84 | ->defaultNull() 85 | ->end() 86 | ->integerNode('private') 87 | ->beforeNormalization() 88 | ->ifString() 89 | ->then($parseOctal) 90 | ->end() 91 | ->defaultNull() 92 | ->end() 93 | ->end() 94 | ->end() 95 | ->arrayNode('dir') 96 | ->children() 97 | ->integerNode('public') 98 | ->beforeNormalization() 99 | ->ifString() 100 | ->then($parseOctal) 101 | ->end() 102 | ->defaultNull() 103 | ->end() 104 | ->integerNode('private') 105 | ->beforeNormalization() 106 | ->ifString() 107 | ->then($parseOctal) 108 | ->end() 109 | ->defaultNull() 110 | ->end() 111 | ->end() 112 | ->end() 113 | ->end() 114 | ->end() 115 | ->scalarNode('mimeTypeDetector')->defaultNull()->end() 116 | ->end() 117 | ; 118 | } 119 | 120 | /** 121 | * Backward compatibility (BC) between symfony/yaml <= 5.4 and >= 6.0. 122 | * 123 | * @see https://github.com/symfony/symfony/pull/34813 124 | */ 125 | private static function parseOctal(string $scalar): int 126 | { 127 | if (!preg_match('/^(?:\+|-)?0o?(?P[0-7_]++)$/', $scalar, $matches)) { 128 | throw new \InvalidArgumentException("The scalar \"$scalar\" is not a valid octal number."); 129 | } 130 | 131 | $value = str_replace('_', '', $matches['value']); 132 | 133 | if ('-' === $scalar[0]) { 134 | return (int) -octdec($value); 135 | } 136 | 137 | return (int) octdec($value); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/DependencyInjection/Factory/AdapterFactoryInterface.php: -------------------------------------------------------------------------------- 1 | load('factories.xml'); 26 | 27 | $adapterFactories = $this->getFactories($container); 28 | 29 | $configuration = new Configuration($adapterFactories); 30 | $config = $this->processConfiguration($configuration, $configs); 31 | 32 | $loader->load('adapters.xml'); 33 | $loader->load('flysystem.xml'); 34 | 35 | $adapters = []; 36 | 37 | foreach ($config['adapters'] as $name => $adapter) { 38 | $adapters[$name] = $this->createAdapter($name, $adapter, $container, $adapterFactories); 39 | } 40 | 41 | foreach ($config['filesystems'] as $name => $filesystem) { 42 | $this->createFilesystem($name, $filesystem, $container, $adapters); 43 | } 44 | } 45 | 46 | public function getConfiguration(array $config, ContainerBuilder $container): Configuration 47 | { 48 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 49 | $loader->load('factories.xml'); 50 | 51 | $adapterFactories = $this->getFactories($container); 52 | 53 | return new Configuration($adapterFactories); 54 | } 55 | 56 | private function createAdapter(string $name, array $config, ContainerBuilder $container, array $factories): string 57 | { 58 | foreach ($config as $key => $adapter) { 59 | if (\array_key_exists($key, $factories)) { 60 | $id = \sprintf('oneup_flysystem.%s_adapter', $name); 61 | $factories[$key]->create($container, $id, $adapter); 62 | 63 | return $id; 64 | } 65 | } 66 | 67 | throw new \LogicException(\sprintf('The adapter \'%s\' is not configured.', $name)); 68 | } 69 | 70 | private function createFilesystem(string $name, array $config, ContainerBuilder $container, array $adapters): Reference 71 | { 72 | if (!\array_key_exists($config['adapter'], $adapters)) { 73 | throw new \LogicException(\sprintf('The adapter \'%s\' is not defined.', $config['adapter'])); 74 | } 75 | 76 | $adapter = $adapters[$config['adapter']]; 77 | $id = \sprintf('oneup_flysystem.%s_filesystem', $name); 78 | 79 | $tagParams = ['key' => $name]; 80 | 81 | if ($config['mount']) { 82 | $tagParams['mount'] = $config['mount']; 83 | } 84 | 85 | $options = []; 86 | 87 | if (\array_key_exists('visibility', $config)) { 88 | $options[Config::OPTION_VISIBILITY] = $config['visibility']; 89 | } 90 | 91 | if (\array_key_exists('directory_visibility', $config)) { 92 | $options[Config::OPTION_DIRECTORY_VISIBILITY] = $config['directory_visibility']; 93 | } 94 | 95 | $container 96 | ->setDefinition($id, new ChildDefinition('oneup_flysystem.filesystem')) 97 | ->replaceArgument(0, new Reference($adapter)) 98 | ->replaceArgument(1, $options) 99 | ->addTag('oneup_flysystem.filesystem', $tagParams) 100 | ->setPublic(true) 101 | ; 102 | 103 | if (!empty($config['alias'])) { 104 | $container->getDefinition($id)->setPublic(false); 105 | 106 | try { 107 | $alias = $container->setAlias($config['alias'], $id); 108 | } catch (InvalidArgumentException $exception) { 109 | $alias = $container->getAlias($config['alias']); 110 | } 111 | 112 | $alias->setPublic(true); 113 | } 114 | 115 | $aliasName = $name; 116 | 117 | if (!preg_match('~filesystem$~i', $aliasName)) { 118 | $aliasName .= 'Filesystem'; 119 | } 120 | 121 | $container->registerAliasForArgument($id, FilesystemOperator::class, $aliasName)->setPublic(false); 122 | 123 | return new Reference($id); 124 | } 125 | 126 | private function getFactories(ContainerBuilder $container): array 127 | { 128 | return $this->getAdapterFactories($container); 129 | } 130 | 131 | private function getAdapterFactories(ContainerBuilder $container): array 132 | { 133 | if (null !== $this->adapterFactories) { 134 | return $this->adapterFactories; 135 | } 136 | 137 | $factories = []; 138 | $services = $container->findTaggedServiceIds('oneup_flysystem.adapter_factory'); 139 | 140 | foreach (array_keys($services) as $id) { 141 | /** @var FactoryInterface $factory */ 142 | $factory = $container->get($id); 143 | $factories[(string) str_replace('-', '_', $factory->getKey())] = $factory; 144 | } 145 | 146 | return $this->adapterFactories = $factories; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/OneupFlysystemBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new FilesystemPass()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Resources/config/adapters.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/Resources/config/factories.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | 27 | 29 | 30 | 31 | 33 | 34 | 35 | 37 | 38 | 39 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Resources/config/flysystem.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Filesystems --> 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/App/Kernel.php: -------------------------------------------------------------------------------- 1 | load(__DIR__ . '/config/config.yml'); 25 | } 26 | 27 | public function getProjectDir(): string 28 | { 29 | return __DIR__; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/App/config/config.yml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | 6 | Oneup\FlysystemBundle\Tests\DependencyInjection\TestService: 7 | public: true 8 | 9 | google_cloud_storage_client: 10 | class: Google\Cloud\Storage\StorageClient 11 | 12 | framework: 13 | translator: { fallback: en } 14 | secret: secret 15 | router: 16 | resource: '%kernel.project_dir%/config/routing.yml' 17 | strict_requirements: '%kernel.debug%' 18 | default_locale: en 19 | session: ~ 20 | test: true 21 | trusted_hosts: ~ 22 | 23 | oneup_flysystem: 24 | adapters: 25 | local: 26 | local: 27 | location: '%kernel.cache_dir%/1up' 28 | permissions: 29 | file: 30 | public: 0o644 31 | private: 0o600 32 | dir: 33 | public: 0o755 34 | private: 0o700 35 | 36 | memory: 37 | memory: ~ 38 | 39 | async_aws_s3: 40 | async_aws_s3: 41 | client: 'test' 42 | bucket: 'test' 43 | 44 | custom: 45 | custom: 46 | service: 'test' 47 | 48 | ftp: 49 | ftp: 50 | options: 51 | host: hostname # required 52 | root: /root/path/ # required 53 | username: username # required 54 | password: password # required 55 | 56 | gitlab: 57 | gitlab: 58 | client: 'test' 59 | 60 | sftp: 61 | sftp: 62 | options: 63 | host: localhost 64 | username: foo 65 | root: '/upload' 66 | 67 | googlecloudstorage: 68 | googlecloudstorage: 69 | client: 'google_cloud_storage_client' 70 | bucket: 'test' 71 | prefix: 'prefix' 72 | 73 | filesystems: 74 | myfilesystem: 75 | adapter: local 76 | 77 | myfilesystem2: 78 | adapter: local 79 | visibility: public 80 | mount: 'local' 81 | 82 | myfilesystem3: 83 | adapter: local 84 | visibility: private 85 | 86 | myfilesystem4: 87 | adapter: googlecloudstorage 88 | 89 | myfilesystem5: 90 | adapter: local 91 | 92 | myfilesystem6: 93 | adapter: local 94 | directory_visibility: public 95 | -------------------------------------------------------------------------------- /tests/DependencyInjection/Compiler/FilesystemPassTest.php: -------------------------------------------------------------------------------- 1 | get('oneup_flysystem.mount_manager'); 17 | /** @var Filesystem $filesystem1 */ 18 | $filesystem1 = self::getContainer()->get('oneup_flysystem.myfilesystem_filesystem'); 19 | /** @var Filesystem $filesystem2 */ 20 | $filesystem2 = self::getContainer()->get('oneup_flysystem.myfilesystem2_filesystem'); 21 | 22 | self::assertFalse($filesystem1->fileExists('foo')); 23 | self::assertFalse($filesystem2->fileExists('bar')); 24 | 25 | $mountManager->write('myfilesystem://foo', 'foo'); 26 | $mountManager->write('local://bar', 'bar'); 27 | 28 | self::assertTrue($filesystem1->fileExists('foo')); 29 | self::assertTrue($filesystem2->fileExists('bar')); 30 | 31 | $mountManager->delete('myfilesystem://foo'); 32 | $mountManager->delete('local://bar'); 33 | 34 | self::assertFalse($filesystem1->fileExists('foo')); 35 | self::assertFalse($filesystem2->fileExists('bar')); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/DependencyInjection/OneupFlysystemExtensionTest.php: -------------------------------------------------------------------------------- 1 | get('oneup_flysystem.myfilesystem_filesystem'); 32 | 33 | /** 34 | * Visibility flag is set to "public". 35 | * 36 | * @var Filesystem $filesystem2 37 | */ 38 | $filesystem2 = self::getContainer()->get('oneup_flysystem.myfilesystem2_filesystem'); 39 | 40 | /** 41 | * Visibility flag is set to "private". 42 | * 43 | * @var Filesystem $filesystem3 44 | */ 45 | $filesystem3 = self::getContainer()->get('oneup_flysystem.myfilesystem3_filesystem'); 46 | 47 | $filesystem1->write('1/meep', 'meep\'s content'); 48 | $filesystem2->write('2/meep', 'meep\'s content'); 49 | $filesystem3->write('3/meep', 'meep\'s content'); 50 | 51 | self::assertSame(Visibility::PUBLIC, $filesystem1->visibility('1/meep')); 52 | self::assertSame(Visibility::PUBLIC, $filesystem2->visibility('2/meep')); 53 | self::assertSame(Visibility::PRIVATE, $filesystem3->visibility('3/meep')); 54 | 55 | $filesystem1->delete('1/meep'); 56 | $filesystem1->delete('2/meep'); 57 | $filesystem1->delete('3/meep'); 58 | } 59 | 60 | public function testDirectoryVisibilitySettings(): void 61 | { 62 | if (version_compare((string) InstalledVersions::getVersion('league/flysystem'), '2.3.1', '<')) { 63 | $this->markTestSkipped('Flysystem >= 2.3.1 is required (see https://github.com/thephpleague/flysystem/pull/1368).'); 64 | } 65 | 66 | /** 67 | * No directory visibility flag set, default to "private". 68 | * 69 | * @var Filesystem $filesystem5 70 | */ 71 | $filesystem5 = self::getContainer()->get('oneup_flysystem.myfilesystem5_filesystem'); 72 | 73 | /** 74 | * Visibility flag is set to "public". 75 | * 76 | * @var Filesystem $filesystem6 77 | */ 78 | $filesystem6 = self::getContainer()->get('oneup_flysystem.myfilesystem6_filesystem'); 79 | 80 | $filesystem5->createDirectory('5'); 81 | $filesystem6->createDirectory('6'); 82 | 83 | /** @var DirectoryAttributes $directory5Attributes */ 84 | [$directory5Attributes] = $filesystem5->listContents('')->filter(static fn (StorageAttributes $attributes) => $attributes->isDir() && '5' === $attributes->path())->toArray(); 85 | /** @var DirectoryAttributes $directory6Attributes */ 86 | [$directory6Attributes] = $filesystem5->listContents('')->filter(static fn (StorageAttributes $attributes) => $attributes->isDir() && '6' === $attributes->path())->toArray(); 87 | 88 | self::assertSame(Visibility::PRIVATE, $directory5Attributes->visibility()); 89 | self::assertSame(Visibility::PUBLIC, $directory6Attributes->visibility()); 90 | } 91 | 92 | public function testAdapterAvailability(): void 93 | { 94 | /** @var \SimpleXMLElement $adapters */ 95 | $adapters = simplexml_load_string((string) file_get_contents(__DIR__ . '/../../src/Resources/config/adapters.xml')); 96 | 97 | foreach ($adapters->children()->children() as $service) { 98 | if (null === $service->attributes()) { 99 | continue; 100 | } 101 | 102 | foreach ($service->attributes() as $key => $attribute) { 103 | if ('class' === (string) $key) { 104 | self::assertTrue(class_exists((string) $attribute), 'Could not load class: ' . $attribute); 105 | } 106 | } 107 | } 108 | } 109 | 110 | public function testGetConfiguration(): void 111 | { 112 | $extension = new OneupFlysystemExtension(); 113 | $configuration = $extension->getConfiguration([], new ContainerBuilder()); 114 | 115 | self::assertInstanceOf('Oneup\FlysystemBundle\DependencyInjection\Configuration', $configuration); 116 | } 117 | 118 | public function testServiceAliasWithFilesystemSuffix(): void 119 | { 120 | $container = $this->loadExtension([ 121 | 'oneup_flysystem' => [ 122 | 'adapters' => [ 123 | 'default_adapter' => [ 124 | 'local' => [ 125 | 'location' => '.', 126 | 'permissions' => [ 127 | 'file' => [ 128 | 'public' => '0644', 129 | 'private' => '0644', 130 | ], 131 | 'dir' => [ 132 | 'public' => '0755', 133 | 'private' => '0700', 134 | ], 135 | ], 136 | ], 137 | ], 138 | ], 139 | 'filesystems' => [ 140 | 'acme_filesystem' => [ 141 | 'alias' => Filesystem::class, 142 | 'adapter' => 'default_adapter', 143 | ], 144 | ], 145 | ], 146 | ]); 147 | 148 | $aliasName = 'League\Flysystem\FilesystemOperator $acmeFilesystem'; 149 | 150 | self::assertTrue($container->hasAlias($aliasName)); 151 | self::assertSame('oneup_flysystem.acme_filesystem_filesystem', (string) $container->getAlias($aliasName)); 152 | 153 | self::assertTrue($container->hasAlias(Filesystem::class)); 154 | self::assertSame('oneup_flysystem.acme_filesystem_filesystem', (string) $container->getAlias(Filesystem::class)); 155 | } 156 | 157 | public function testServiceAliasWithoutFilesystemSuffix(): void 158 | { 159 | $container = $this->loadExtension([ 160 | 'oneup_flysystem' => [ 161 | 'adapters' => [ 162 | 'default_adapter' => [ 163 | 'local' => [ 164 | 'location' => '.', 165 | ], 166 | ], 167 | ], 168 | 'filesystems' => [ 169 | 'acme' => [ 170 | 'alias' => Filesystem::class, 171 | 'adapter' => 'default_adapter', 172 | ], 173 | ], 174 | ], 175 | ]); 176 | 177 | $aliasName = 'League\Flysystem\FilesystemOperator $acmeFilesystem'; 178 | 179 | self::assertTrue($container->hasAlias($aliasName)); 180 | self::assertSame('oneup_flysystem.acme_filesystem', (string) $container->getAlias($aliasName)); 181 | 182 | self::assertTrue($container->hasAlias(Filesystem::class)); 183 | self::assertSame('oneup_flysystem.acme_filesystem', (string) $container->getAlias(Filesystem::class)); 184 | } 185 | 186 | public function testServiceAliasInjection(): void 187 | { 188 | /** @var TestService $testService */ 189 | $testService = self::getContainer()->get(TestService::class); 190 | 191 | self::assertInstanceOf(TestService::class, $testService); 192 | self::assertInstanceOf(Filesystem::class, $testService->filesystem); 193 | } 194 | 195 | public function testGoogleCloudAdapter(): void 196 | { 197 | $this->assertInstanceOf(Filesystem::class, self::getContainer()->get('oneup_flysystem.myfilesystem4_filesystem')); 198 | } 199 | 200 | private function loadExtension(array $config): ContainerBuilder 201 | { 202 | $extension = new OneupFlysystemExtension(); 203 | $extension->load($config, $container = new ContainerBuilder()); 204 | 205 | return $container; 206 | } 207 | } 208 | 209 | /** 210 | * @internal 211 | */ 212 | final class TestService 213 | { 214 | public FilesystemOperator $filesystem; 215 | 216 | public function __construct(FilesystemOperator $myfilesystem) 217 | { 218 | $this->filesystem = $myfilesystem; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |