Skip to content

Commit 409f306

Browse files
committed
feat: make builder options discoverable
1 parent 856dbe6 commit 409f306

File tree

50 files changed

+1496
-932
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1496
-932
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ to interact with your storage.
115115
8. [BunnyCDN](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/8-bunnycdn.md)
116116

117117
* [Security issue disclosure procedure](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/A-security-disclosure-procedure.md)
118-
* [Configuration reference](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/B-configuration-reference.md)
119118

120119
## Security Issues
121120

docs/1-getting-started.md

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
## Installation
99

10-
flysystem-bundle requires PHP 7.1+ and Symfony 4.2+.
10+
flysystem-bundle requires PHP 8.0+ and Symfony 5.4+.
1111

1212
You can install the bundle using Symfony Flex:
1313

@@ -26,8 +26,7 @@ use Flysystem in your application as soon as you install the bundle:
2626
flysystem:
2727
storages:
2828
default.storage:
29-
adapter: 'local'
30-
options:
29+
local:
3130
directory: '%kernel.project_dir%/var/storage/default'
3231
```
3332
@@ -100,13 +99,11 @@ autowired arguments. For example:
10099
flysystem:
101100
storages:
102101
users.storage:
103-
adapter: 'local'
104-
options:
102+
local:
105103
directory: '%kernel.project_dir%/storage/users'
106104
107105
projects.storage:
108-
adapter: 'local'
109-
options:
106+
local:
110107
directory: '%kernel.project_dir%/storage/projects'
111108
```
112109

@@ -151,8 +148,7 @@ Then, you can overwrite your storages in the test environment:
151148
flysystem:
152149
storages:
153150
users.storage:
154-
adapter: 'local'
155-
options:
151+
local:
156152
directory: '%kernel.project_dir%/storage/users'
157153
```
158154

@@ -162,7 +158,7 @@ flysystem:
162158
flysystem:
163159
storages:
164160
users.storage:
165-
adapter: 'memory'
161+
memory: ~
166162
```
167163

168164
This configuration will swap every reference to the `users.storage` service (or to the
@@ -186,8 +182,7 @@ And then, you can configure your storage with the `readonly` options.
186182
flysystem:
187183
storages:
188184
users.storage:
189-
adapter: 'local'
190-
options:
185+
local:
191186
directory: '%kernel.project_dir%/storage/users'
192187
readonly: true
193188
```

docs/2-cloud-storage-providers.md

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ composer require league/flysystem-azure-blob-storage
2828
flysystem:
2929
storages:
3030
users.storage:
31-
adapter: 'azure'
32-
options:
31+
azure:
3332
client: 'azure_client_service' # The service ID of the MicrosoftAzure\Storage\Blob\BlobRestProxy instance
3433
container: 'container_name'
3534
prefix: 'optional/path/prefix'
@@ -51,8 +50,7 @@ composer require league/flysystem-async-aws-s3
5150
flysystem:
5251
storages:
5352
users.storage:
54-
adapter: 'asyncaws'
55-
options:
53+
asyncaws:
5654
client: 'aws_client_service' # The service ID of the AsyncAws\S3\S3Client instance
5755
bucket: 'bucket_name'
5856
prefix: 'optional/path/prefix'
@@ -74,9 +72,8 @@ composer require league/flysystem-aws-s3-v3
7472
flysystem:
7573
storages:
7674
users.storage:
77-
adapter: 'aws'
7875
# visibility: public # Make the uploaded file publicly accessible in S3
79-
options:
76+
aws:
8077
client: 'aws_client_service' # The service ID of the Aws\S3\S3Client instance
8178
bucket: 'bucket_name'
8279
prefix: 'optional/path/prefix'
@@ -99,8 +96,7 @@ composer require league/flysystem-google-cloud-storage
9996
flysystem:
10097
storages:
10198
users.storage:
102-
adapter: 'gcloud'
103-
options:
99+
gcloud:
104100
client: 'gcloud_client_service' # The service ID of the Google\Cloud\Storage\StorageClient instance
105101
bucket: 'bucket_name'
106102
prefix: 'optional/path/prefix'
@@ -126,8 +122,7 @@ services:
126122
flysystem:
127123
storages:
128124
cdn.storage:
129-
adapter: 'asyncaws'
130-
options:
125+
asyncaws:
131126
client: 'digitalocean_spaces_client'
132127
bucket: '%env(DIGITALOCEAN_SPACES_BUCKET)%'
133128
```
@@ -152,8 +147,7 @@ services:
152147
flysystem:
153148
storages:
154149
cdn.storage:
155-
adapter: 'asyncaws'
156-
options:
150+
asyncaws:
157151
client: 'scaleway_spaces_client'
158152
bucket: '%env(SCALEWAY_SPACES_BUCKET)%'
159153
```
@@ -178,8 +172,7 @@ services:
178172
flysystem:
179173
storages:
180174
cdn.storage:
181-
adapter: 'asyncaws'
182-
options:
175+
asyncaws:
183176
client: 'cloudflare_r2_client'
184177
bucket: '%env(CLOUDFLARE_R2_BUCKET)%'
185178
```

docs/3-interacting-with-ftp-and-sftp-servers.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ composer require league/flysystem-ftp
2020
flysystem:
2121
storages:
2222
backup.storage:
23-
adapter: 'ftp'
24-
options:
23+
ftp:
2524
host: 'ftp.example.com'
2625
username: 'username'
2726
password: 'password'
@@ -50,8 +49,7 @@ composer require league/flysystem-sftp-v3
5049
flysystem:
5150
storages:
5251
backup.storage:
53-
adapter: 'sftp'
54-
options:
52+
sftp:
5553
host: 'example.com'
5654
port: 22
5755
username: 'username'

docs/4-using-lazy-adapter-to-switch-at-runtime.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,20 @@ services:
4242
flysystem:
4343
storages:
4444
uploads.storage.aws:
45-
adapter: 'aws'
46-
options:
45+
aws:
4746
client: 'Aws\S3\S3Client'
4847
bucket: 'my-bucket'
4948
prefix: '%env(S3_STORAGE_PREFIX)%'
5049

5150
uploads.storage.local:
52-
adapter: 'local'
53-
options:
51+
local:
5452
directory: '%kernel.project_dir%/var/storage/uploads'
5553

5654
uploads.storage.memory:
57-
adapter: 'memory'
55+
memory: ~
5856

5957
uploads.storage:
60-
adapter: 'lazy'
61-
options:
58+
lazy:
6259
source: '%env(APP_UPLOADS_SOURCE)%'
6360
```
6461

docs/5-creating-a-custom-adapter.md

Lines changed: 198 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,201 @@ storages:
3232
flysystem:
3333
storages:
3434
users.storage:
35-
adapter: 'App\Flysystem\MyCustomAdapter'
36-
```
35+
service: 'App\Flysystem\MyCustomAdapter'
36+
```
37+
38+
## Creating a custom adapter builder (advanced)
39+
40+
For more complex custom adapters that require configuration validation, IDE auto-completion,
41+
and integration with the bundle's configuration system, you can create a custom adapter builder.
42+
43+
This allows you to define your custom adapter directly in the configuration:
44+
45+
```yaml
46+
# config/packages/flysystem.yaml
47+
48+
flysystem:
49+
storages:
50+
users.storage:
51+
my_custom: # Your custom adapter type
52+
option1: 'value1'
53+
option2: 'value2'
54+
```
55+
56+
### Creating the adapter builder
57+
58+
Create a class implementing `AdapterDefinitionBuilderInterface`:
59+
60+
```php
61+
<?php
62+
63+
namespace App\Flysystem\Builder;
64+
65+
use App\Flysystem\MyCustomAdapter;
66+
use League\FlysystemBundle\Adapter\Builder\AdapterDefinitionBuilderInterface;
67+
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
68+
use Symfony\Component\DependencyInjection\ContainerBuilder;
69+
use Symfony\Component\DependencyInjection\Definition;
70+
71+
class MyCustomAdapterDefinitionBuilder implements AdapterDefinitionBuilderInterface
72+
{
73+
public function getName(): string
74+
{
75+
return 'my_custom';
76+
}
77+
78+
public function getRequiredPackages(): array
79+
{
80+
// Return required packages for your adapter
81+
// Format: ['ClassName' => 'vendor/package-name']
82+
return [];
83+
}
84+
85+
public function addConfiguration(NodeDefinition $node): void
86+
{
87+
$node
88+
->children()
89+
->scalarNode('option1')
90+
->isRequired()
91+
->info('Description of option1')
92+
->end()
93+
->scalarNode('option2')
94+
->defaultValue('default_value')
95+
->info('Description of option2')
96+
->end()
97+
->booleanNode('option3')
98+
->defaultFalse()
99+
->info('Description of option3')
100+
->end()
101+
->end();
102+
}
103+
104+
public function createAdapter(ContainerBuilder $container, string $storageName, array $options, ?string $defaultVisibilityForDirectories): string
105+
{
106+
$adapterId = 'flysystem.adapter.' . $storageName;
107+
108+
$definition = new Definition(MyCustomAdapter::class);
109+
$definition->setPublic(false);
110+
111+
// Configure your adapter with the options
112+
$definition->setArgument(0, $options['option1']);
113+
$definition->setArgument(1, $options['option2']);
114+
$definition->setArgument(2, $options['option3']);
115+
116+
$container->setDefinition($adapterId, $definition);
117+
118+
return $adapterId;
119+
}
120+
}
121+
```
122+
123+
### Registering the adapter builder
124+
125+
#### Via Bundle (recommended for reusable bundles)
126+
127+
If you're creating a bundle, register your builder in your bundle class:
128+
129+
```php
130+
<?php
131+
132+
namespace App\MyCustomBundle;
133+
134+
use App\Flysystem\Builder\MyCustomAdapterDefinitionBuilder;
135+
use League\FlysystemBundle\FlysystemBundle;
136+
use Symfony\Component\DependencyInjection\ContainerBuilder;
137+
use Symfony\Component\HttpKernel\Bundle\Bundle;
138+
139+
class MyCustomBundle extends Bundle
140+
{
141+
public function build(ContainerBuilder $container): void
142+
{
143+
parent::build($container);
144+
145+
// Register your custom adapter builder
146+
$extension = $container->getExtension('flysystem');
147+
if ($extension instanceof FlysystemExtension) {
148+
$extension->addAdapterDefinitionBuilder(new MyCustomAdapterDefinitionBuilder());
149+
}
150+
}
151+
}
152+
```
153+
154+
#### Via Kernel (for application-specific adapters)
155+
156+
For application-specific adapters, register your builder in your `Kernel`:
157+
158+
```php
159+
<?php
160+
161+
namespace App;
162+
163+
use App\Flysystem\Builder\MyCustomAdapterDefinitionBuilder;
164+
use League\FlysystemBundle\DependencyInjection\FlysystemExtension;
165+
use Symfony\Component\DependencyInjection\ContainerBuilder;
166+
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
167+
168+
class Kernel extends BaseKernel
169+
{
170+
// ...
171+
172+
protected function build(ContainerBuilder $container): void
173+
{
174+
parent::build($container);
175+
176+
// Register your custom adapter builder
177+
$extension = $container->getExtension('flysystem');
178+
if ($extension instanceof FlysystemExtension) {
179+
$extension->addAdapterDefinitionBuilder(new MyCustomAdapterDefinitionBuilder());
180+
}
181+
}
182+
}
183+
```
184+
185+
Once registered, you can use the `debug:config flysystem` command to see your custom adapter
186+
and all its available options in the configuration tree.
187+
188+
### Testing your custom builder
189+
190+
Create a test class extending `AbstractAdapterDefinitionBuilderTest`:
191+
192+
```php
193+
<?php
194+
195+
namespace Tests\App\Flysystem\Builder;
196+
197+
use App\Flysystem\Builder\MyCustomAdapterDefinitionBuilder;
198+
use App\Flysystem\MyCustomAdapter;
199+
use League\FlysystemBundle\Test\AbstractAdapterDefinitionBuilderTest;
200+
use Symfony\Component\DependencyInjection\Definition;
201+
202+
class MyCustomAdapterDefinitionBuilderTest extends AbstractAdapterDefinitionBuilderTest
203+
{
204+
protected function createBuilder(): MyCustomAdapterDefinitionBuilder
205+
{
206+
return new MyCustomAdapterDefinitionBuilder();
207+
}
208+
209+
public static function provideValidOptions(): \Generator
210+
{
211+
yield 'minimal' => [[
212+
'option1' => 'value1',
213+
]];
214+
215+
yield 'full' => [[
216+
'option1' => 'value1',
217+
'option2' => 'custom_value',
218+
'option3' => true,
219+
]];
220+
}
221+
222+
protected function assertDefinition(Definition $definition): void
223+
{
224+
$this->assertSame(MyCustomAdapter::class, $definition->getClass());
225+
$this->assertSame('value1', $definition->getArgument(0));
226+
$this->assertSame('custom_value', $definition->getArgument(1));
227+
$this->assertTrue($definition->getArgument(2));
228+
}
229+
}
230+
```
231+
232+
This provides comprehensive testing of your builder's configuration and adapter creation logic.

0 commit comments

Comments
 (0)